From 8418f3d4fc2a09a6d197d98db49f20ea5842e7fd Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Wed, 4 Nov 2026 20:34:57 +0000 Subject: [PATCH 01/57] Define original dual-core ESP32 identity and qualification gates --- CHANGELOG.md | 6 ++ docs/STATUS.md | 9 +++ docs/engineering/esp32-dual-core.md | 117 ++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 docs/engineering/esp32-dual-core.md diff --git a/CHANGELOG.md b/CHANGELOG.md index d035e1722..9128c6bb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Original ESP32 dual-core target investigation + +- Added board-identification requirements and staged acceptance gates for a + distinct Xtensa LX6 ESP32 target. No runtime support or verified board pinout + is claimed; the exact marketplace board/module still needs identification. + ### A library sorted the way an EDA sorts it - Parts are filed by category and sub-category as Proteus's device picker diff --git a/docs/STATUS.md b/docs/STATUS.md index 8bb7ecc85..bea0c655c 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -5,6 +5,15 @@ maintained by hand until evidence tooling exists. It is not a generated support matrix. Specifications define target contracts; their acceptance does not mean the described behaviour exists. +## Original ESP32 dual-core target investigation (2026-11-04) + +The owner requested an original dual-core ESP32 development board. The listing +name does not identify its PCB/module revision. [Identity and implementation +gates](engineering/esp32-dual-core.md) document the new Xtensa LX6 backend, +dual-core SoC, board circuits, radio, low-power and qualification work needed. +This is design investigation only: no runnable ESP32 target is added, and the +existing RISC-V ESP32-C3 is not a substitute. + ## The library's categories (2026-11-03) Parts are filed by category and sub-category as Proteus's device picker and diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md new file mode 100644 index 000000000..911bf717c --- /dev/null +++ b/docs/engineering/esp32-dual-core.md @@ -0,0 +1,117 @@ +# Original ESP32 dual-core target: identity and implementation gates + +**Status: design investigation; no executable target is added.** Requested by + the owner on 2026-11-04. This is the original ESP32 family, not ESP32-C3, +ESP32-S2 or ESP32-S3. The generic marketplace title “ESP-32 / ESP-32S +WiFi+Bluetooth Dual Cores” does not identify a reproducible board revision. +The product link, module marking and board pinout are still required. + +## Reference material + +Primary references inspected for this investigation: + +- [ESP32 Series Datasheet](https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf): + chip selection, Xtensa LX6 CPUs, memory, peripheral inventory, pin restrictions, + electrical characteristics and power modes. +- [ESP32 Technical Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf): + memory and peripheral register behavior, reset, interrupts and ULP execution. +- [ESP32-DevKitC V4 user guide](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32/esp32-devkitc/user_guide.html): + a reference board family, not an identification of the owner's board. +- [Espressif QEMU guide](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/tools/qemu.html): + possible independent execution referee. Under the current reuse assessment, + QEMU is a separate referee, not code to copy into the native runtime. + +Record the exact document versions, hashes, schematic revision, silicon revision +and vendor SDK/toolchain versions in the eventual target fixtures. The URLs above +can change. Vendor maximum ratings are not normal operating conditions. + +## Establish identity before catalog publication + +The implementation needs a tuple, not a marketing name: + +| Layer | Required evidence | +|---|---| +| Board | Manufacturer, product ID, PCB revision, header count/order, front/back labels and schematic | +| Module | Exact ESP-WROOM/ESP32-WROOM/ESP32-WROVER/ESP-32S marking, module pin map and module datasheet | +| Silicon | Exact part and revision, CPU count, applicable errata and eFuse defaults | +| Storage | Actual flash part/capacity/mode, and PSRAM presence/part/capacity | +| Power | Regulator, input routing/protection, capacitors, enable/reset network and current limits | +| USB | Actual USB-to-UART bridge and auto-reset transistor network | +| Controls | BOOT/EN contacts, pull resistors, LED connections/polarity and any extra devices | + +Do not assume a 30-pin clone and a 38-pin DevKitC share a connector map, regulator, +USB bridge or LED pin. Do not expose module flash/PSRAM pins as free GPIOs. +If evidence is unavailable, label that property unresolved rather than choosing +a plausible component. WROOM and WROVER variants need separate descriptions. + +## Existing runtime and reusable boundaries + +The current ESP32-C3 has a RISC-V backend and C3-specific register models in +`core/mcu/esp32c3/`. The original ESP32 requires Xtensa LX6 instruction and +exception semantics. There is no Xtensa constructor in `core/mcu/build.c`. +Renaming the C3 target cannot execute ESP32 firmware correctly. + +Reuse the scheduler, buses, board composition, electrical solver, instruments, +record/replay, transport and debugger infrastructure. Review individual C3 +algorithms for shared concepts, but implement ESP32 register addresses, reset +values, interrupts and pin matrix against its own TRM. Existing multi-core +machinery is a starting point, not evidence of correct ESP32 SMP behavior. + +## Implementation sequence and acceptance + +Each gate is a coherent implementation PR with declared limitations and tests. +No unimplemented target appears as runnable in the part picker. + +| Gate | Implementation | Acceptance evidence | +|---|---|---| +| 1. CPU | Xtensa LX6 configured instruction set, density instructions, register windows, ABI, special registers, exceptions, interrupts, atomics, memory ordering and debug registers | Instruction fixtures and unmodified toolchain-generated call/window/exception/atomic programs; unknown instructions stop with PC/opcode diagnostics | +| 2. Dual-core SoC | PRO_CPU/APP_CPU reset/release/stall, separate architectural state, shared/aliased memory, arbitration, interrupt matrix, inter-core interrupts and synchronization | Deterministic two-core shared-memory and spinlock tests; core-local reset and exception tests; record/replay equality | +| 3. Boot/storage | ROM provenance/licensing, strapping/eFuse behavior, SPI flash commands, image loader, partition/bootloader flow, cache/MMU, optional PSRAM | Unmodified pinned ESP-IDF bootloader and app, invalid image failures, flash read/write/erase and cache invalidation tests; no host execution of firmware | +| 4. Clocks/reset | Oscillators, PLL/APB/RTC clocks, reset causes/domains, watchdogs, brownout and clock switching | Register/timing fixtures; reset-retention matrix; watchdog and supply-failure recovery without erasing retained state accidentally | +| 5. Pins | GPIO/IO MUX/matrix, pads, drive strength, pulls, input-only pads, strapping latch and analog routing | Physical-header continuity, input thresholds/output loading, floating/conflicting driver warnings, boot with external strap loads | +| 6. Peripherals | UARTs, SPI, I2C, I2S, timers, LEDC, RMT, MCPWM, pulse counter, ADCs, DACs, touch, SD/MMC and documented DMA paths | Every declared mode tied to register fixtures and unmodified vendor examples; external electrical peers on actual pins | +| 7. Security/system | Interrupts, RNG, crypto engines, eFuses, flash encryption/secure boot as applicable to the identified revision | Known-answer vectors, reset/access protection and negative tests; deterministic test seeds explicitly distinguished from hardware entropy | +| 8. Low power | Modem/light/deep sleep, wake sources, RTC memory/peripherals, ULP, clock/domain retention | Wake timing, retained/lost state, ULP program and pin wake tests; current-consumption models qualified separately from functional sleep | +| 9. Radio | Wi-Fi station/AP and supported modes, Bluetooth Classic BR/EDR and BLE, coexistence and controller interfaces | Unmodified SDK examples and peer traffic; connection loss/retry, concurrent traffic and timing fixtures; document packet-level versus RF/analog fidelity | +| 10. Board | Exact module, power path, bridge/auto-reset, capacitors/resistors, buttons/LEDs and header connections | Schematic-derived netlist, loaded supply and reset traces, serial programming/console workflow, button interactions while firmware runs | +| 11. Product | ELF/flash loading, toolchain profile, GDB per-core state/breakpoints, traces, watches, schematic artwork and controls | Native CLI and GUI run the same image; cross-platform integration, undo/control interaction and saved-project compatibility | +| 12. Qualification | Arduino-ESP32 and ESP-IDF applications, SMP FreeRTOS, peripheral combinations and hardware comparisons | Pinned reproducible builds on Linux/macOS/Windows; full coverage inventory, raw traces and tolerances; no unexplained unsupported-register accesses | + +The peripheral inventory must be expanded into every TRM block and revision-specific +mode before declaring the SoC complete. The table is a work breakdown, not a +claim that all edge cases have already been enumerated. Nominal CPU execution +and peripheral timing accuracy must be reported separately; do not claim +cycle accuracy from successful boot alone. + +## Electrical and visual contract + +- GPIOs participate in circuit loading, including pad restrictions, configured + drive strength and pulls. GPIOs are not ideal rail-to-rail sources. +- ADC attenuation/calibration/nonlinearity, source impedance, sampling behavior + and ADC2/radio arbitration require explicit qualification. DAC and touch + readings must come from connected circuit behavior, not fabricated values. +- BOOT and EN operate during execution. Their electrical networks determine + reset/strapping effects. Serial DTR/RTS follows the actual board circuit. +- Supply sag, brownout and reset must have consistent effects on firmware, + peripherals and schematic indicators. A power LED is driven by its circuit. +- Readable pin names, boot/power warnings and live states belong in the schematic. + A generic illustration must not imply a verified header layout. +- “Ultra-low power” requires identified measurement conditions and validated + consumption models; it is not inferred from the product title. + +## ADR-030 answers and release rule + +Real operation is defined by the identified board schematic, silicon TRM, +module datasheet, errata and pinned vendor firmware. Observable behavior includes +both cores' software state, physical pin voltages/currents/timing, boot/reset, +host serial traffic, radio peers and sleep/wake behavior. Full implementation +means the gate inventory and documented mode/edge-case coverage, not a boot demo. +Internal abstractions are permitted only with explicit externally tested bounds; +packet radio models do not establish RF propagation fidelity. Proof combines +instruction fixtures, vendor firmware, independent referees and hardware traces. +No hardware trace has been collected for this new target yet. + +Until those gates pass, report the target's exact supported subset and missing +features. This investigation establishes neither board support nor real-hardware +equivalence. The exact board identity blocks the final board netlist; it does not +block research on the shared Xtensa/SoC foundation. From 9ca6b4d8ef2fc86781506d869c2f65c2586b3193 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Wed, 4 Nov 2026 20:37:10 +0000 Subject: [PATCH 02/57] Identify TECNOIOT ESP32 listing and transcribe 30-pin headers --- docs/STATUS.md | 4 +- docs/engineering/esp32-dual-core.md | 6 +- docs/engineering/esp32-tecnoiot-b07n97dd1b.md | 81 +++++++++++++++++++ 3 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 docs/engineering/esp32-tecnoiot-b07n97dd1b.md diff --git a/docs/STATUS.md b/docs/STATUS.md index bea0c655c..e15f85560 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -8,7 +8,9 @@ does not mean the described behaviour exists. ## Original ESP32 dual-core target investigation (2026-11-04) The owner requested an original dual-core ESP32 development board. The listing -name does not identify its PCB/module revision. [Identity and implementation +is now tied to TECNOIOT ASIN B07N97DD1B: the advertised photo shows a +30-pin ESP-WROOM-32 board. Exact PCB revision and BOM remain unverified. +[Identity and implementation gates](engineering/esp32-dual-core.md) document the new Xtensa LX6 backend, dual-core SoC, board circuits, radio, low-power and qualification work needed. This is design investigation only: no runnable ESP32 target is added, and the diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 911bf717c..87c2a6beb 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -4,7 +4,11 @@ the owner on 2026-11-04. This is the original ESP32 family, not ESP32-C3, ESP32-S2 or ESP32-S3. The generic marketplace title “ESP-32 / ESP-32S WiFi+Bluetooth Dual Cores” does not identify a reproducible board revision. -The product link, module marking and board pinout are still required. +The owner supplied [Amazon ASIN B07N97DD1B](https://www.amazon.fr/dp/B07N97DD1B). +The listing brand is TECNOIOT. Its main image shows an ESP-WROOM-32 shield, +30 header positions, Micro-USB, EN and BOOT buttons. This identifies the +advertised form factor, not a guaranteed PCB revision or bill of materials. +See the [listing evidence and header transcription](esp32-tecnoiot-b07n97dd1b.md). ## Reference material diff --git a/docs/engineering/esp32-tecnoiot-b07n97dd1b.md b/docs/engineering/esp32-tecnoiot-b07n97dd1b.md new file mode 100644 index 000000000..dc4f7a3f2 --- /dev/null +++ b/docs/engineering/esp32-tecnoiot-b07n97dd1b.md @@ -0,0 +1,81 @@ +# TECNOIOT B07N97DD1B: advertised board evidence + +**Status: identified listing, provisional hardware inventory, not executable +support.** Inspected 2026-11-04 after the owner provided the product link. +No physical unit, PCB schematic or hardware capture has been examined. + +## Sources and confidence + +- [Owner's Amazon listing, ASIN B07N97DD1B](https://www.amazon.fr/dp/B07N97DD1B): + brand TECNOIOT; generic original ESP32 dual-core Wi-Fi/Bluetooth description. + Product text requests the CP210X driver and links Espressif's ESP32 datasheet, + TRM and Nicholas3388/LuaNode. That repository link does not prove which PCB + this seller ships. +- [Listing's main photograph](https://m.media-amazon.com/images/I/61siWrmZEVL._AC_SL1239_.jpg): + ESP-WROOM-32 module shield, two rows of 15 pads, Micro-USB connector, + EN and BOOT switches. Header labels are transcribed below. The regulator + appears marked AMS1117; its full suffix/rating and the circuit remain unverified. +- [Espressif ESP32 Series Datasheet](https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf) + supplies SoC restrictions. It is not a schematic for this development board. + +Do not call this an Espressif DevKitC V4 or claim an authenticated DOIT PCB. +Its 30-pin layout resembles boards commonly sold as ESP32 DevKit V1, but that +name alone does not establish manufacturer, revision or electrical equivalence. +Marketplace images and shipped units can differ. + +## Header transcription + +Orientation: component side facing the viewer, antenna at top, USB connector +at bottom. Position 1 is nearest the antenna in each row. These are local +reference positions, not vendor-assigned physical pin numbers. + +| Position | Left silkscreen | Signal | Right silkscreen | Signal | +|---|---|---|---|---| +| 1 | EN | Chip enable/reset | D23 | GPIO23 | +| 2 | VP | GPIO36 / SENSOR_VP | D22 | GPIO22 | +| 3 | VN | GPIO39 / SENSOR_VN | TX0 | GPIO1 / UART0 TX | +| 4 | D34 | GPIO34 | RX0 | GPIO3 / UART0 RX | +| 5 | D35 | GPIO35 | D21 | GPIO21 | +| 6 | D32 | GPIO32 | D19 | GPIO19 | +| 7 | D33 | GPIO33 | D18 | GPIO18 | +| 8 | D25 | GPIO25 / DAC1 | D5 | GPIO5 | +| 9 | D26 | GPIO26 / DAC2 | TX2 | GPIO17 / default UART2 TX | +| 10 | D27 | GPIO27 | RX2 | GPIO16 / default UART2 RX | +| 11 | D14 | GPIO14 | D4 | GPIO4 | +| 12 | D12 | GPIO12 | D2 | GPIO2 | +| 13 | D13 | GPIO13 | D15 | GPIO15 | +| 14 | GND | Ground | GND | Ground | +| 15 | VIN | Board input rail, routing unverified | 3V3 | Board 3.3 V rail | + +UART labels describe conventional routing; firmware can reconfigure the GPIO +matrix. GPIO34/35/36/39 are input-only and do not provide software-controlled +internal pull-up/pull-down resistors. They must not acquire output drivers in +the simulator. The module's flash connections must not become additional +user headers. Strapping includes GPIO0/2/5/12/15; GPIO0 is not on these headers +but BOOT interaction and reset-time sampling still matter. GPIO12 can affect +flash supply configuration; reproduce behavior for the verified module/eFuses, +not a universal boot-success assumption. GPIO1/3 share the console bridge. + +The photo is insufficient to determine a user LED's GPIO or polarity. Do not +invent a GPIO2 LED because other boards have one. VIN is not evidence of a +safe 5 V output, reverse-power protection, regulator headroom or allowable input +range. GPIOs are 3.3 V signals, not assumed 5 V tolerant. + +## Unresolved hardware evidence + +Before publishing an electrically qualified board model, establish: + +- Actual PCB/module revision and ESP32 silicon/eFuse identity. +- Flash manufacturer/capacity and whether any PSRAM is fitted; neither is proven + by this photograph. Do not silently select a WROVER configuration. +- USB bridge exact device and wiring; CP210X in the product text proves neither + a specific CP2102 variant nor its oscillator, power or flow-control connections. +- Regulator suffix, power routing, protection, capacitor values and tolerances. +- EN/BOOT pulls, RC timing and auto-reset DTR/RTS transistor network. +- LED count, nets, series resistors, polarity and button contact behavior. + +Continuity measurements, clear PCB photos and the correct schematic can resolve +these. Firmware chip/flash identification can supplement them. These unknowns +do not block the shared Xtensa CPU implementation in the +[implementation gates](esp32-dual-core.md), but they do block claiming every +board detail matches the shipped unit. From 9e004a18556e435906a0f8d914b93f2b08689c11 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Wed, 4 Nov 2026 20:42:26 +0000 Subject: [PATCH 03/57] Validate original ESP32 firmware images with native integrity inspection --- CHANGELOG.md | 8 +++ cli/commands.c | 32 ++++++++++ core/mcu/esp32/image.c | 69 ++++++++++++++++++++++ core/mcu/esp32/image.h | 25 ++++++++ docs/STATUS.md | 4 +- docs/engineering/esp32-dual-core.md | 34 ++++++++++- tests/CMakeLists.txt | 2 + tests/protocol/test_esp32_image.py | 90 +++++++++++++++++++++++++++++ 8 files changed, 261 insertions(+), 3 deletions(-) create mode 100644 core/mcu/esp32/image.c create mode 100644 core/mcu/esp32/image.h create mode 100644 tests/protocol/test_esp32_image.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 9128c6bb9..b2175bff8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Original ESP32 firmware inspection + +- `embsim image-info esp32 FILE` validates standalone original-ESP32 plaintext + firmware headers, bounded segments, footer padding, XOR and optional SHA-256. + Reports segment offsets/addresses and raw revision fields without loading code. + Signed/encrypted images, merged flash dumps and board compatibility are outside + this inspector's scope. The original ESP32 remains unavailable for execution. + ### Original ESP32 dual-core target investigation - Added board-identification requirements and staged acceptance gates for a diff --git a/cli/commands.c b/cli/commands.c index 46f5fe192..6fb5761cf 100644 --- a/cli/commands.c +++ b/cli/commands.c @@ -13,6 +13,7 @@ #include "embsim/embsim.h" #include "host/host.h" #include "mcu/analysis.h" +#include "mcu/esp32/image.h" #include "mcu/mcu.h" #include "protocol/protocol.h" #include "session/session_priv.h" @@ -79,6 +80,7 @@ static void usage_to(FILE *out) " board list|info ID [--pins]\n" " mcu pins ID [PIN] [--json] the alternate functions and ADC channels on each pin\n" " mcu signal ID SIGNAL [--json] the pins and AF numbers that carry a signal (TIM2_CH3)\n" + " image-info esp32 FILE validate a standalone original-ESP32 image and report segments\n" " models list the model primitives\n" " version [--json]\n" " --help, --version\n" @@ -1955,6 +1957,35 @@ static int cmd_models(const char *argv0) return 0; } +static int cmd_esp32_image(int argc, char **argv) +{ + esk_alloc a; + char *data = NULL, why[256]; size_t len = 0; unsigned i; + esk_esp32_image image; + es_status st; + if (argc != 2 || strcmp(argv[0], "esp32")) { + fputs("usage: embsim image-info esp32 FILE\n", stderr); return 2; + } + esk_alloc_init(&a, NULL); + if (esk_host_read_file(&a, argv[1], &data, &len) != ES_OK) { + fputs("embsim: cannot read firmware image\n", stderr); return 2; + } + st = esk_esp32_image_inspect((const unsigned char *)data, len, &image, why, sizeof why); + esk_free(&a, data, len + 1); + if (st != ES_OK) { fprintf(stderr, "embsim: %s\n", why); return 2; } + printf("Original ESP32 image: entry 0x%08lx, %u segments, XOR verified, SHA-256 %s\n", + (unsigned long)image.entry, image.count, image.has_digest ? "verified" : "absent"); + printf("SPI flash mode %u, size code %u, frequency code %u; revision legacy %u, min %u, max %u (raw)\n", + image.flash_mode, image.flash_size_code, image.flash_frequency_code, + image.min_revision_legacy, image.min_revision, image.max_revision); + for (i = 0; i < image.count; i++) + printf(" segment %u: address 0x%08lx, %lu bytes, file offset %lu\n", i, + (unsigned long)image.segment[i].address, (unsigned long)image.segment[i].length, + (unsigned long)image.segment[i].offset); + puts("Integrity only: does not establish memory-map validity, authenticity, board compatibility or executable target support."); + return 0; +} + int embsim_cli_main(int argc, char **argv); int embsim_cli_main(int argc, char **argv) { @@ -1981,6 +2012,7 @@ int embsim_cli_main(int argc, char **argv) if (!strcmp(cmd, "toolchains")) return cmd_toolchains(argv[0]); if (!strcmp(cmd, "catalog")) return cmd_catalog(argc - 2, argv + 2, argv[0]); if (!strcmp(cmd, "board")) return cmd_board(argc - 2, argv + 2, argv[0]); + if (!strcmp(cmd, "image-info")) return cmd_esp32_image(argc - 2, argv + 2); if (!strcmp(cmd, "models")) return cmd_models(argv[0]); if (!strcmp(cmd, "mcu")) return cmd_mcu(argc - 2, argv + 2, argv[0]); if (!strcmp(cmd, "version") || !strcmp(cmd, "--version")) { diff --git a/core/mcu/esp32/image.c b/core/mcu/esp32/image.c new file mode 100644 index 000000000..307823031 --- /dev/null +++ b/core/mcu/esp32/image.c @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* Original implementation from Espressif's documented firmware image format: + * docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/firmware-image-format.html + * No ROM or third-party loader code is embedded. See image.h for scope. */ +#include "mcu/esp32/image.h" +#include "util/sha256.h" +#include +#include + +static uint32_t le32(const unsigned char *p) +{ + return (uint32_t)p[0] | (uint32_t)p[1] << 8 | (uint32_t)p[2] << 16 | (uint32_t)p[3] << 24; +} +static unsigned le16(const unsigned char *p) { return (unsigned)p[0] | (unsigned)p[1] << 8; } +static es_status bad(char *why, size_t cap, const char *message) +{ + if (why && cap) snprintf(why, cap, "%s", message); + return ES_EINVAL; +} +es_status esk_esp32_image_inspect(const unsigned char *data, size_t len, + esk_esp32_image *out, char *why, size_t cap) +{ + esk_esp32_image v; + size_t pos = 24, i, k, footer; + unsigned checksum = 0xef; + if (why && cap) why[0] = 0; + if (!out) return bad(why, cap, "An output descriptor is required."); + memset(out, 0, sizeof *out); memset(&v, 0, sizeof v); + if (!data || len < 24 || len > 67108864) return bad(why, cap, "Expected a standalone ESP32 image of 24 bytes to 64 MiB."); + if (data[0] != 0xe9) return bad(why, cap, "Expected ESP image magic E9; ELF, encrypted and merged flash images are not accepted."); + if (!data[1] || data[1] > 16) return bad(why, cap, "ESP image segment count must be 1 to 16."); + if (le16(data + 12) != 0) return bad(why, cap, "Image is not for the original ESP32 (chip ID 0)."); + if (data[23] > 1) return bad(why, cap, "Invalid appended-digest flag."); + if (data[2] > 3 || (data[3] >> 4) > 4 || ((data[3] & 15) > 2 && (data[3] & 15) != 15)) + return bad(why, cap, "Unsupported SPI flash mode, size or frequency encoding."); + v.count = data[1]; v.entry = le32(data + 4); v.flash_mode = data[2]; + v.flash_size_code = (unsigned)data[3] >> 4; v.flash_frequency_code = data[3] & 15u; + v.wp_pin = data[8]; for (i = 0; i < 3; i++) v.drive[i] = data[9 + i]; + v.min_revision_legacy = data[14]; v.min_revision = le16(data + 15); v.max_revision = le16(data + 17); + v.has_digest = data[23]; + for (i = 0; i < v.count; i++) { + uint32_t address, size; + if (len - pos < 8) return bad(why, cap, "Truncated segment header."); + address = le32(data + pos); size = le32(data + pos + 4); pos += 8; + if ((uint64_t)address + size > UINT64_C(0x100000000)) return bad(why, cap, "Segment address wraps the 32-bit address space."); + if ((size_t)size > len - pos) return bad(why, cap, "Truncated segment data."); + v.segment[i].address = address; v.segment[i].length = size; v.segment[i].offset = pos; + for (k = 0; k < size; k++) checksum ^= data[pos + k]; + pos += size; + } + /* The checksum is the last byte of the next 16-byte block, including + * when segment data itself already ends on a 16-byte boundary. */ + footer = pos + (15 - (pos & 15u)); + if (footer >= len) return bad(why, cap, "Truncated checksum footer."); + for (i = pos; i < footer; i++) if (data[i]) return bad(why, cap, "Nonzero image footer padding."); + if (data[footer] != checksum) return bad(why, cap, "ESP image segment XOR checksum mismatch."); + v.checksum_offset = footer; + pos = footer + 1; + if (v.has_digest) { + esk_sha256 hash; unsigned char digest[32]; + if (len - pos < 32) return bad(why, cap, "Truncated appended SHA-256 digest."); + esk_sha256_init(&hash); esk_sha256_update(&hash, data, pos); esk_sha256_final(&hash, digest); + if (memcmp(digest, data + pos, 32)) return bad(why, cap, "ESP image appended SHA-256 mismatch."); + pos += 32; + } + if (pos != len) return bad(why, cap, "Trailing bytes: merged dumps, signed images and appended data are not supported by this inspector."); + *out = v; return ES_OK; +} diff --git a/core/mcu/esp32/image.h b/core/mcu/esp32/image.h new file mode 100644 index 000000000..8ba8b531f --- /dev/null +++ b/core/mcu/esp32/image.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_ESP32_IMAGE_H +#define ESK_ESP32_IMAGE_H +#include "embsim/types.h" + +/* Original ESP32 standalone plaintext image, not a merged flash dump, ELF, + * encrypted image or secure-boot signature. No memory is mapped or executed. + * Segment addresses and revision bounds are reported, not board-qualified. + * On failure *out is zero; on success segment offsets borrow the input bytes. */ +typedef struct esk_esp32_image_segment { + uint32_t address, length; + size_t offset; +} esk_esp32_image_segment; +typedef struct esk_esp32_image { + uint32_t entry; + unsigned count, flash_mode, flash_size_code, flash_frequency_code; + unsigned wp_pin, drive[3], min_revision_legacy, min_revision, max_revision; + int has_digest; + size_t checksum_offset; + esk_esp32_image_segment segment[16]; +} esk_esp32_image; +es_status esk_esp32_image_inspect(const unsigned char *data, size_t len, + esk_esp32_image *out, char *why, size_t cap); +#endif diff --git a/docs/STATUS.md b/docs/STATUS.md index e15f85560..411a0abb8 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -13,7 +13,9 @@ is now tied to TECNOIOT ASIN B07N97DD1B: the advertised photo shows a [Identity and implementation gates](engineering/esp32-dual-core.md) document the new Xtensa LX6 backend, dual-core SoC, board circuits, radio, low-power and qualification work needed. -This is design investigation only: no runnable ESP32 target is added, and the +Native `image-info esp32` now validates standalone image headers, segments, XOR +checksum and optional SHA-256, with explicit rejection of signed/encrypted/merged +images. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. ## The library's categories (2026-11-03) diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 87c2a6beb..38ad3bdc5 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -1,6 +1,6 @@ # Original ESP32 dual-core target: identity and implementation gates -**Status: design investigation; no executable target is added.** Requested by +**Status: firmware inspection implemented; no executable target is added.** Requested by the owner on 2026-11-04. This is the original ESP32 family, not ESP32-C3, ESP32-S2 or ESP32-S3. The generic marketplace title “ESP-32 / ESP-32S WiFi+Bluetooth Dual Cores” does not identify a reproducible board revision. @@ -29,6 +29,36 @@ Record the exact document versions, hashes, schematic revision, silicon revision and vendor SDK/toolchain versions in the eventual target fixtures. The URLs above can change. Vendor maximum ratings are not normal operating conditions. +## Implemented: standalone firmware integrity inspection + +`embsim image-info esp32 app.bin` inspects a plaintext original-ESP32 image +without loading or executing it. It reports the entry point, SPI flash header +encodings, raw chip-revision bounds and every segment's load address, payload +file offset and length. It rejects wrong chip IDs, invalid format fields, +truncation, 32-bit address wrap, bad padding/checksum/digest and trailing data. +The output descriptor is cleared on failure; the source file is never modified. + +The checksum covers segment data with Espressif's initial XOR byte `0xEF`; +optional SHA-256 covers the header through checksum. An absent digest is reported, +not manufactured. An intact digest establishes integrity, not authenticity. +Segment addresses and revision fields are reported without claiming that the +requested PCB, silicon or memory map accepts them. Zero-length/dummy segments +are retained. Unsupported SPI encodings fail explicitly. + +Scope is one standalone plaintext image, at most 64 MiB and 16 segments. Merged +flash dumps/partition tables, ELF conversion, encrypted or signed images, secure +boot verification and flash placement are not implemented in this inspector. +The existing C3 raw-flash loader is unchanged. This is not an ESP32 boot demo. + +Format reference: [Espressif's firmware image specification](https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/firmware-image-format.html). +The C parser is original code with no vendor ROM or loader copied. Acceptance +uses independent Python `struct`/`hashlib` fixtures, truncation at every byte, +16 segments, checksum-preserving data corruption, wrong-chip IDs, field failures +and untouched-input checks. A separate esptool 5.6.0 comparison accepted our +fixture's checksum/hash and generated ten files accepted by the native inspector +(with/without digest and zero to 1024-byte payloads). These synthetic files prove +format compatibility, not firmware execution or physical-board equivalence. + ## Establish identity before catalog publication The implementation needs a tuple, not a marketing name: @@ -116,6 +146,6 @@ instruction fixtures, vendor firmware, independent referees and hardware traces. No hardware trace has been collected for this new target yet. Until those gates pass, report the target's exact supported subset and missing -features. This investigation establishes neither board support nor real-hardware +features. This implementation establishes neither board support nor real-hardware equivalence. The exact board identity blocks the final board netlist; it does not block research on the shared Xtensa/SoC foundation. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 51fb8315f..36e869a13 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -20,6 +20,8 @@ add_test(NAME catalog_fixtures COMMAND embsim_cli catalog validate ${CMAKE_SOURC # protocol conformance suite over the stdio and WebSocket transports. find_package(Python3 COMPONENTS Interpreter QUIET) if(Python3_Interpreter_FOUND) + add_test(NAME esp32_image COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tests/protocol/test_esp32_image.py + --embsim $ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) add_test(NAME electrical_feedback COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tests/protocol/test_electrical_feedback.py --embsim $ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) add_test(NAME tools_unittests COMMAND ${Python3_EXECUTABLE} -m unittest discover -s ${CMAKE_SOURCE_DIR}/tools -p "test_*.py" diff --git a/tests/protocol/test_esp32_image.py b/tests/protocol/test_esp32_image.py new file mode 100644 index 000000000..0e88bdf04 --- /dev/null +++ b/tests/protocol/test_esp32_image.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""Original ESP32 image inspection; independent struct/hashlib format fixtures.""" +import argparse +import hashlib +from pathlib import Path +import struct +import subprocess +import tempfile + + +def image(segments, digest=True, chip=0): + h = bytearray(24) + h[:4] = bytes([0xe9, len(segments), 2, 0x20]) + struct.pack_into(' Date: Wed, 4 Nov 2026 20:47:05 +0000 Subject: [PATCH 04/57] Implement experimental Xtensa integer execution with assembler-referenced acceptance --- CHANGELOG.md | 10 ++++ core/mcu/xtensa/integer.c | 45 ++++++++++++++++ core/mcu/xtensa/integer.h | 14 +++++ docs/STATUS.md | 6 ++- docs/engineering/esp32-dual-core.md | 41 +++++++++++++- tests/unit/test_xtensa_integer.c | 65 ++++++++++++++++++++++ tests/vendor/README.md | 9 ++++ tests/vendor/xtensa_integer/run.py | 84 +++++++++++++++++++++++++++++ 8 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 core/mcu/xtensa/integer.c create mode 100644 core/mcu/xtensa/integer.h create mode 100644 tests/unit/test_xtensa_integer.c create mode 100644 tests/vendor/xtensa_integer/run.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b2175bff8..05452e145 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental Xtensa integer execution + +- Added the wide integer arithmetic/logic/immediate/low-multiply execution + kernel, with unsigned wraparound semantics, operand-alias handling and + unchanged register state on unsupported instructions. This is not a CPU + backend: fetch, control flow, memory, register windows, exceptions, interrupts, + density, FPU, SMP and timing are still absent; no runnable target is added. +- Added native boundary/sequence checks and an external-assembler referee for + 4,352 instruction cases; no assembler code or tables are embedded. + ### Original ESP32 firmware inspection - `embsim image-info esp32 FILE` validates standalone original-ESP32 plaintext diff --git a/core/mcu/xtensa/integer.c b/core/mcu/xtensa/integer.c new file mode 100644 index 000000000..af7fd11bc --- /dev/null +++ b/core/mcu/xtensa/integer.c @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* Original integer semantics; encoding fixtures assembled independently by GNU + * binutils, used as an external tool only. No decoder tables copied. */ +#include "mcu/xtensa/integer.h" + +/* Sign-extension as unsigned arithmetic avoids signed overflow/conversions. */ +static uint32_t extend(uint32_t x, unsigned bits) +{ + uint32_t sign = UINT32_C(1) << (bits - 1); + return (x ^ sign) - sign; +} +es_status esk_xtensa_integer_execute(uint32_t a[16], const unsigned char *code, + size_t available, size_t *consumed) +{ + uint32_t insn, result; + unsigned op, t, s, r, op1, op2, dest; + if (consumed) *consumed = 0; + if (!a || !code || !consumed) return ES_EINVAL; + if (available < 3) return ES_EINVAL; + insn = (uint32_t)code[0] | (uint32_t)code[1] << 8 | (uint32_t)code[2] << 16; + op = insn & 15u; t = (insn >> 4) & 15u; s = (insn >> 8) & 15u; + r = (insn >> 12) & 15u; op1 = (insn >> 16) & 15u; op2 = insn >> 20; + dest = r; + if (op == 2 && r == 10) { + dest = t; result = extend((insn >> 16) | (uint32_t)s << 8, 12); + } else if (op == 2 && (r == 12 || r == 13)) { + uint32_t immediate = extend(insn >> 16, 8); + dest = t; result = a[s] + (r == 13 ? immediate << 8 : immediate); + } else if (op == 0 && op1 == 0 && op2 >= 8) { + unsigned shift = op2 & 3u; + uint32_t left = a[s] << shift; + result = op2 < 12 ? left + a[t] : left - a[t]; + } else if (op == 0 && op1 == 0 && op2 >= 1 && op2 <= 3) { + result = op2 == 1 ? a[s] & a[t] : op2 == 2 ? a[s] | a[t] : a[s] ^ a[t]; + } else if (op == 0 && op1 == 0 && op2 == 6 && s <= 1) { + result = s == 0 || (a[t] & UINT32_C(0x80000000)) ? 0u - a[t] : a[t]; + } else if (op == 0 && op1 == 2 && op2 == 8) { + result = a[s] * a[t]; + } else { + return ES_EUNSUPPORTED; + } + a[dest] = result; *consumed = 3; + return ES_OK; +} diff --git a/core/mcu/xtensa/integer.h b/core/mcu/xtensa/integer.h new file mode 100644 index 000000000..d9fe9fe2d --- /dev/null +++ b/core/mcu/xtensa/integer.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_XTENSA_INTEGER_H +#define ESK_XTENSA_INTEGER_H +#include "embsim/types.h" +/* Experimental LX6 little-endian integer execution kernel. The caller supplies + * the currently visible A0..A15 window. This is NOT a CPU backend: no fetch, + * windows, interrupts/exceptions, memory, control flow, density, FPU, special + * registers, SMP or timing. Unsupported encodings never change the registers. + * 24-bit ADD/SUB/ADDX/SUBX, AND/OR/XOR, MOVI/ADDI/ADDMI, NEG/ABS and MULL only. + * No instruction timing is inferred. Success consumes three bytes. */ +es_status esk_xtensa_integer_execute(uint32_t a[16], const unsigned char *code, + size_t available, size_t *consumed); +#endif diff --git a/docs/STATUS.md b/docs/STATUS.md index 411a0abb8..4a79409c9 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -15,7 +15,11 @@ gates](engineering/esp32-dual-core.md) document the new Xtensa LX6 backend, dual-core SoC, board circuits, radio, low-power and qualification work needed. Native `image-info esp32` now validates standalone image headers, segments, XOR checksum and optional SHA-256, with explicit rejection of signed/encrypted/merged -images. No runnable ESP32 target is added, and the +images. An experimental, unregistered Xtensa integer kernel now executes the +wide arithmetic/logic/immediate/low-multiply subset against a supplied register +window: 84 native assertions and 4,352 separately assembled all-register comparisons +pass. Fetch, control flow, memory, windows, interrupts, density, SMP and timing +remain unimplemented. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. ## The library's categories (2026-11-03) diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 38ad3bdc5..0647a2e96 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -1,6 +1,6 @@ # Original ESP32 dual-core target: identity and implementation gates -**Status: firmware inspection implemented; no executable target is added.** Requested by +**Status: firmware inspection and experimental integer kernel implemented; no executable target is added.** Requested by the owner on 2026-11-04. This is the original ESP32 family, not ESP32-C3, ESP32-S2 or ESP32-S3. The generic marketplace title “ESP-32 / ESP-32S WiFi+Bluetooth Dual Cores” does not identify a reproducible board revision. @@ -29,6 +29,42 @@ Record the exact document versions, hashes, schematic revision, silicon revision and vendor SDK/toolchain versions in the eventual target fixtures. The URLs above can change. Vendor maximum ratings are not normal operating conditions. +## Implemented: experimental Xtensa integer execution kernel + +`core/mcu/xtensa/integer.c` executes a bounded subset of little-endian, wide +(24-bit) instructions against a caller-provided A0–A15 register window: +ADD, SUB, ADDX2/4/8, SUBX2/4/8, AND, OR, XOR, MOVI, ADDI, ADDMI, NEG, +ABS and MULL. MOV's OR encoding is included. Arithmetic wraps modulo 2^32, +including ABS/NEG of the most negative integer; sign extension uses unsigned +arithmetic. Destinations may alias either source, and A0 is writable. + +Unsupported instructions and truncated/invalid inputs fail without changing any +register; successful execution reports three consumed bytes. This helper has no +fetch, memory operations, control flow, register-window rotation, interrupts, +exceptions, special registers, density instructions, FPU, SMP or timing model. +It is not wired into the CPU constructor or catalog and cannot run firmware. +The caller-provided window is not a claim that register-window behavior exists. + +`test_xtensa_integer` checks an assembled sequence, arithmetic extremes, aliases, +input errors and unchanged state on unsupported execution (84 assertions). +`tests/vendor/xtensa_integer/run.py` independently assembles 4,352 instruction +cases with GNU binutils and compares every output register against Python +integer arithmetic. It varies register aliases, immediates and 32-bit extremes +with a fixed seed. GNU xtensa-lx106 binutils 2.40 was used solely as an external +encoding referee for the shared subset; it is not an LX6 system emulator or an +ESP32 qualification suite. No assembler code or decoder tables are copied. + +Reproduce on Linux with a host C compiler and separate Xtensa binutils: + +```sh +python tests/vendor/xtensa_integer/run.py \ + --assembler /path/to/xtensa-lx106-elf-as \ + --objcopy /path/to/xtensa-lx106-elf-objcopy +``` + +The deterministic native unit test runs in CTest without those external tools. +Hardware execution comparison and a complete LX6 ISA referee remain outstanding. + ## Implemented: standalone firmware integrity inspection `embsim image-info esp32 app.bin` inspects a plaintext original-ESP32 image @@ -82,7 +118,8 @@ a plausible component. WROOM and WROVER variants need separate descriptions. The current ESP32-C3 has a RISC-V backend and C3-specific register models in `core/mcu/esp32c3/`. The original ESP32 requires Xtensa LX6 instruction and -exception semantics. There is no Xtensa constructor in `core/mcu/build.c`. +exception semantics. There is no Xtensa CPU constructor in `core/mcu/build.c`; the experimental +integer kernel below is not a runtime backend. Renaming the C3 target cannot execute ESP32 firmware correctly. Reuse the scheduler, buses, board composition, electrical solver, instruments, diff --git a/tests/unit/test_xtensa_integer.c b/tests/unit/test_xtensa_integer.c new file mode 100644 index 000000000..9e425cd1b --- /dev/null +++ b/tests/unit/test_xtensa_integer.c @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/xtensa/integer.h" + +/* Bytes are generated by external GNU xtensa-lx106-elf-as 2.40, using + * .begin no-transform. These integer encodings are shared with LX6. + * This does not qualify the LX106 configuration as an ESP32 CPU. */ +static void execute(uint32_t a[16], uint32_t word) +{ + unsigned char bytes[3]; size_t n = 99; + bytes[0] = (unsigned char)(word & 255u); + bytes[1] = (unsigned char)((word >> 8) & 255u); + bytes[2] = (unsigned char)((word >> 16) & 255u); + CHECK_EQ(esk_xtensa_integer_execute(a, bytes, sizeof bytes, &n), ES_OK); + CHECK_EQ(n, 3); +} +static void program(void) +{ + uint32_t a[16] = {0}; + execute(a, 0xefaf22); CHECK_EQ(a[2], UINT32_C(0xffffffef)); /* movi a2,-17 */ + execute(a, 0xfdc232); CHECK_EQ(a[3], UINT32_C(0xffffffec)); /* addi a3,a2,-3 */ + execute(a, 0xffd342); CHECK_EQ(a[4], UINT32_C(0xfffffeec)); /* addmi a4,a3,-256 */ + execute(a, 0x805230); CHECK_EQ(a[5], UINT32_C(0xffffffdb)); + execute(a, 0xc06230); CHECK_EQ(a[6], 3); + execute(a, 0x107230); CHECK_EQ(a[7], UINT32_C(0xffffffec)); + execute(a, 0x208230); CHECK_EQ(a[8], UINT32_C(0xffffffef)); + execute(a, 0x309230); CHECK_EQ(a[9], 3); + execute(a, 0x90a230); CHECK_EQ(a[10], UINT32_C(0xffffffca)); + execute(a, 0xa0b230); CHECK_EQ(a[11], UINT32_C(0xffffffa8)); + execute(a, 0xb0c230); CHECK_EQ(a[12], UINT32_C(0xffffff64)); + execute(a, 0xd0d230); CHECK_EQ(a[13], UINT32_C(0xfffffff2)); + execute(a, 0xe0e230); CHECK_EQ(a[14], UINT32_C(0xffffffd0)); + execute(a, 0xf0f230); CHECK_EQ(a[15], UINT32_C(0xffffff8c)); + execute(a, 0x604020); CHECK_EQ(a[4], 17); + execute(a, 0x604120); CHECK_EQ(a[4], 17); + execute(a, 0x824230); CHECK_EQ(a[4], 340); + execute(a, 0x204220); CHECK_EQ(a[4], a[2]); /* mov alias */ +} +static void boundaries(void) +{ + uint32_t a[16] = {0}, before[16]; unsigned char bytes[3] = {0, 0, 0}; size_t n; + a[2] = UINT32_C(0xffffffff); a[3] = 1; + execute(a, 0x805230); CHECK_EQ(a[5], 0); /* wrap */ + a[2] = UINT32_C(0x80000000); + execute(a, 0x604020); CHECK_EQ(a[4], UINT32_C(0x80000000)); + execute(a, 0x604120); CHECK_EQ(a[4], UINT32_C(0x80000000)); + a[2] = 7; execute(a, 0x604120); CHECK_EQ(a[4], 7); + a[2] = UINT32_C(0xffffffff); a[3] = UINT32_C(0xffffffff); + execute(a, 0x824230); CHECK_EQ(a[4], 1); + execute(a, 0x00a802); CHECK_EQ(a[0], UINT32_C(0xfffff800)); /* movi a0,-2048 */ + execute(a, 0xffa712); CHECK_EQ(a[1], 2047); /* movi a1,2047 */ + memcpy(before, a, sizeof a); + CHECK_EQ(esk_xtensa_integer_execute(a, bytes, 3, &n), ES_EUNSUPPORTED); + CHECK_EQ(n, 0); CHECK_EQ(memcmp(before, a, sizeof a), 0); + CHECK_EQ(esk_xtensa_integer_execute(a, bytes, 2, &n), ES_EINVAL); + CHECK_EQ(n, 0); CHECK_EQ(memcmp(before, a, sizeof a), 0); + CHECK_EQ(esk_xtensa_integer_execute(NULL, bytes, 3, &n), ES_EINVAL); + CHECK_EQ(esk_xtensa_integer_execute(a, NULL, 3, &n), ES_EINVAL); + CHECK_EQ(esk_xtensa_integer_execute(a, bytes, 3, NULL), ES_EINVAL); +} +TEST_MAIN_BEGIN + (void)t_str_eq; /* This test uses only numeric assertions from the shared harness. */ + RUN(program); RUN(boundaries); +TEST_MAIN_END diff --git a/tests/vendor/README.md b/tests/vendor/README.md index 46d3f6130..215d10836 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -20,3 +20,12 @@ toolchains. | `arduino-esp32/` | Arduino-ESP32 3.2.0 through arduino-cli, as the IDE builds for "ESP32C3 Dev Module": the IDE's Blink, DigitalReadSerial and Fade, the core's GetChipID, RepeatTimer, WiFiScan and WiFiClientConnect, the BLE library's Server and iBeacon, HTTPClient's BasicHttpClient | `emblink/esp32c3_devkitm1` | `vendor_run.py` is what their runners share. + +## Experimental Xtensa integer kernel + +`xtensa_integer/run.py` uses a separate GNU Xtensa assembler and objcopy, plus +Python arithmetic, to check 4,352 wide integer instructions against the original +C kernel. Run with `--assembler PATH --objcopy PATH` on Linux with a host C +compiler. No toolchain is vendored or linked. LX106 binutils supports this +shared integer subset; it does not qualify the full ESP32 LX6 configuration. +See [scope and reproduction](../../docs/engineering/esp32-dual-core.md). diff --git a/tests/vendor/xtensa_integer/run.py b/tests/vendor/xtensa_integer/run.py new file mode 100644 index 000000000..962fd7fc9 --- /dev/null +++ b/tests/vendor/xtensa_integer/run.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""Cross-check the experimental integer kernel with separately assembled bytes. + +GNU binutils is an external encoding referee, never linked or copied. LX106 +binutils can assemble this shared integer subset; it is not an LX6 system referee. +Requires a host C compiler supporting -shared/-fPIC and Python ctypes (Linux). +""" +import argparse +import ctypes +from pathlib import Path +import random +import subprocess +import tempfile + +ROOT = Path(__file__).resolve().parents[3] +MASK = 2**32-1 + + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument('--assembler', required=True) + ap.add_argument('--objcopy', required=True) + ap.add_argument('--cc', default='cc') + args = ap.parse_args() + rng = random.Random(0x32) + cases = [] + operations = ['add', 'sub', 'and', 'or', 'xor', 'addx2', 'addx4', 'addx8', + 'subx2', 'subx4', 'subx8', 'mull', 'neg', 'abs', 'movi', 'addi', 'addmi'] + for operation in operations: + for k in range(256): + regs = [rng.getrandbits(32) for _ in range(16)] + d, s, t = [rng.randrange(16) for _ in range(3)] + if k < 5: + regs[s] = [0, 1, 0x7fffffff, 0x80000000, 0xffffffff][k] + regs[t] = regs[s] + x, y = regs[s], regs[t] + instruction = f'{operation} a{d},a{s},a{t}' + if operation == 'add': result = x+y + elif operation == 'sub': result = x-y + elif operation == 'and': result = x & y + elif operation == 'or': result = x | y + elif operation == 'xor': result = x ^ y + elif operation.startswith('addx'): result = x*int(operation[-1])+y + elif operation.startswith('subx'): result = x*int(operation[-1])-y + elif operation == 'mull': result = x*y + elif operation == 'neg': + instruction = f'neg a{d},a{s}'; result = -x + elif operation == 'abs': + instruction = f'abs a{d},a{s}'; result = abs(x if x < 2**31 else x-2**32) + elif operation == 'movi': + immediate = rng.randrange(-2048, 2048) + instruction = f'movi a{d},{immediate}'; result = immediate + else: + immediate = rng.randrange(-128, 128) * (256 if operation == 'addmi' else 1) + instruction = f'{operation} a{d},a{s},{immediate}'; result = x+immediate + expected = list(regs); expected[d] = result & MASK + cases.append((instruction, regs, expected)) + with tempfile.TemporaryDirectory(prefix='embsim-xtensa-integer-') as tmp: + out = Path(tmp) + source = out/'integer.s'; obj = out/'integer.o'; binary = out/'integer.bin' + source.write_text('.text\n.begin no-transform\n'+ '\n'.join(x[0] for x in cases)+'\n.end no-transform\n', encoding='utf-8') + subprocess.run([args.assembler, str(source), '-o', str(obj)], check=True) + subprocess.run([args.objcopy, '-O', 'binary', '--only-section=.text', str(obj), str(binary)], check=True) + data = binary.read_bytes() + assert len(data) == len(cases)*3, (len(data), len(cases)) + lib = out/'integer.so' + subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', + '-I'+str(ROOT/'core'), '-I'+str(ROOT/'include'), + str(ROOT/'core/mcu/xtensa/integer.c'), '-o', str(lib)], check=True) + execute = ctypes.CDLL(str(lib)).esk_xtensa_integer_execute + execute.argtypes = [ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_ubyte), ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)] + execute.restype = ctypes.c_int + for i, (instruction, registers, expected) in enumerate(cases): + regs = (ctypes.c_uint32*16)(*registers) + encoded = (ctypes.c_ubyte*3).from_buffer_copy(data[i*3:i*3+3]); consumed = ctypes.c_size_t() + assert execute(regs, encoded, 3, ctypes.byref(consumed)) == 0, instruction + assert consumed.value == 3 and list(regs) == expected, (instruction, list(regs), expected) + print(f'{len(cases)} independently assembled integer instructions pass all-register comparisons') + + +if __name__ == '__main__': + main() From c4c908f41b68a4c80d1ab640d5582e87e4c14c6d Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Wed, 4 Nov 2026 21:07:44 +0000 Subject: [PATCH 05/57] Execute mixed-width Xtensa control flow and memory with compiler-generated acceptance --- CHANGELOG.md | 16 +++ core/mcu/xtensa/execute.c | 110 +++++++++++++++ core/mcu/xtensa/execute.h | 28 ++++ core/mcu/xtensa/integer.c | 27 ++++ core/mcu/xtensa/integer.h | 8 +- docs/STATUS.md | 12 +- docs/engineering/esp32-dual-core.md | 77 +++++++++- tests/unit/test_xtensa_execute.c | 156 +++++++++++++++++++++ tests/unit/test_xtensa_integer.c | 18 ++- tests/vendor/README.md | 15 +- tests/vendor/xtensa_integer/run.py | 9 +- tests/vendor/xtensa_integer/run_c.py | 100 +++++++++++++ tests/vendor/xtensa_integer/run_execute.py | 153 ++++++++++++++++++++ 13 files changed, 714 insertions(+), 15 deletions(-) create mode 100644 core/mcu/xtensa/execute.c create mode 100644 core/mcu/xtensa/execute.h create mode 100644 tests/unit/test_xtensa_execute.c create mode 100644 tests/vendor/xtensa_integer/run_c.py create mode 100644 tests/vendor/xtensa_integer/run_execute.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 05452e145..9c3779f94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental Xtensa fetch, flow and memory execution + +- Execute mixed-width instructions through a callback bus, compact arithmetic, + non-windowed jumps/calls/returns, signed/unsigned conditional branches, + byte/halfword/word loads/stores and default PC-relative literal loads. +- Preserve PC/registers on unsupported or failed accesses and report failing + PC/address/opcode; reject unaligned data accesses before touching the bus. +- Added assembled loop/subroutine acceptance, native boundary/fault checks and + 3,840 externally assembled execution cases plus 960 failed-access cases. +- Immediate shifts, EXTUI and signed/unsigned immediate comparisons allow + compiler-generated sum/sort/copy routines to run: 96 executions verify complete + buffers, guards, CALL0 callee-save behavior and returns. + This remains an experimental execution engine, not a registered CPU/board: + full ISA, register windows, exceptions/interrupts, SMP, SoC boot, peripherals + and timing are still absent. + ### Experimental Xtensa integer execution - Added the wide integer arithmetic/logic/immediate/low-multiply execution diff --git a/core/mcu/xtensa/execute.c b/core/mcu/xtensa/execute.c new file mode 100644 index 000000000..bc55e418d --- /dev/null +++ b/core/mcu/xtensa/execute.c @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* Semantics: Cadence Xtensa ISA Summary, instruction descriptions. */ +#include "mcu/xtensa/execute.h" +#include "mcu/xtensa/integer.h" +#include + +static uint32_t sx(uint32_t x, unsigned bits) +{ + uint32_t b = UINT32_C(1) << (bits - 1); return (x ^ b) - b; +} +static es_status fail(esk_xtensa_fault *f, unsigned kind, uint32_t pc, uint32_t addr, uint32_t opcode) +{ + if (f) { f->kind = kind; f->pc = pc; f->address = addr; f->opcode = opcode; } + return kind == ESK_XT_UNSUPPORTED ? ES_EUNSUPPORTED : ES_EINVAL; +} +es_status esk_xtensa_execute(esk_xtensa_state *state, const esk_xtensa_memory *memory, + const unsigned char *code, size_t available, + size_t *consumed, esk_xtensa_fault *fault) +{ + esk_xtensa_state next; + uint32_t word, pc, addr = 0, value = 0; + unsigned op, t, s, r, width, bytes = 0; + int store = 0, signed16 = 0; + size_t used = 0; + es_status st; + if (consumed) *consumed = 0; + if (fault) memset(fault, 0, sizeof *fault); + if (!state || !code || !consumed) return ES_EINVAL; + pc = state->pc; + if (available < 2) return fail(fault, ESK_XT_TRUNCATED, pc, pc, 0); + word = (uint32_t)code[0] | (uint32_t)code[1] << 8; + op = word & 15u; width = op < 8 ? 3u : 2u; + if (available < width) return fail(fault, ESK_XT_TRUNCATED, pc, pc, word); + if (width == 3) word |= (uint32_t)code[2] << 16; + t = (word >> 4) & 15u; s = (word >> 8) & 15u; r = (word >> 12) & 15u; + next = *state; next.pc = pc + width; + st = esk_xtensa_integer_execute(next.a, code, available, &used); + if (st == ES_OK) { next.pc = pc + (uint32_t)used; width = (unsigned)used; } + else if (word == 0x0020f0 || word == 0xf03d) { /* NOP / NOP.N */ } + else if (word == 0x000080 || word == 0xf00d) next.pc = next.a[0]; /* RET/RET.N */ + else if ((word & UINT32_C(0xfff0ff)) == 0x0000a0) next.pc = next.a[s]; /* JX */ + else if ((word & UINT32_C(0xfff0ff)) == 0x0000c0) { /* CALLX0: read before A0 write */ + next.pc = next.a[s]; next.a[0] = pc + 3; + } else if ((word & 63u) == 5) { /* CALL0 */ + next.pc = (pc & UINT32_C(0xfffffffc)) + 4 + (sx(word >> 6, 18) << 2); + next.a[0] = pc + 3; + } else if ((word & 63u) == 6) next.pc = pc + 4 + sx(word >> 6, 18); /* J */ + else if (op == 6 && (t & 3u) == 1) { + int taken = t == 1 ? next.a[s] == 0 : t == 5 ? next.a[s] != 0 : + t == 9 ? (next.a[s] >> 31) != 0 : (next.a[s] >> 31) == 0; + if (taken) next.pc = pc + 4 + sx(word >> 12, 12); + } else if (op == 6 && (t == 2 || t == 6 || t == 10 || t == 14 || t == 11 || t == 15)) { + static const uint32_t constants[16] = { + UINT32_MAX,1,2,3,4,5,6,7,8,10,12,16,32,64,128,256 + }; + uint32_t left = next.a[s], right = constants[r]; int taken; + if (t == 11 || t == 15) { if (r < 2) right = r ? 65536u : 32768u; } + if (t == 10 || t == 14) { left ^= UINT32_C(0x80000000); right ^= UINT32_C(0x80000000); } + taken = t == 2 ? left == right : t == 6 ? left != right : + (t == 10 || t == 11) ? left < right : left >= right; + if (taken) next.pc = pc + 4 + sx(word >> 16, 8); + } else if (op == 7 && (r == 1 || r == 9 || r == 2 || r == 10 || r == 3 || r == 11)) { + uint32_t left = next.a[s], right = next.a[t]; int taken; + if (r == 2 || r == 10) { left ^= UINT32_C(0x80000000); right ^= UINT32_C(0x80000000); } + taken = r == 1 ? left == right : r == 9 ? left != right : + (r == 2 || r == 3) ? left < right : left >= right; + if (taken) next.pc = pc + 4 + sx(word >> 16, 8); + } else if (op == 12 && t >= 8) { + int taken = t < 12 ? next.a[s] == 0 : next.a[s] != 0; + if (taken) next.pc = pc + 4 + ((t & 3u) << 4 | r); + } else if (op == 8 || op == 9) { + bytes = 4; addr = next.a[s] + (uint32_t)r * 4; store = op == 9; + } else if (op == 2 && (r <= 2 || r == 4 || r == 5 || r == 6 || r == 9)) { + unsigned scale = (r == 0 || r == 4) ? 0u : (r == 1 || r == 5 || r == 9) ? 1u : 2u; + bytes = 1u << scale; addr = next.a[s] + ((word >> 16) << scale); + store = r >= 4 && r <= 6; signed16 = r == 9; + } else if (op == 1) { /* L32R without extended LITBASE option */ + bytes = 4; addr = ((pc + 3) & UINT32_C(0xfffffffc)) + ((word >> 8 | UINT32_C(0xffff0000)) << 2); + } else return fail(fault, ESK_XT_UNSUPPORTED, pc, pc, word); + if (bytes) { + if (addr & (bytes - 1u)) return fail(fault, ESK_XT_ALIGN, pc, addr, word); + if (store) { + if (!memory || !memory->write || memory->write(memory->ctx, addr, bytes, next.a[t]) != ES_OK) + return fail(fault, ESK_XT_STORE, pc, addr, word); + } else { + if (!memory || !memory->read || memory->read(memory->ctx, addr, bytes, &value) != ES_OK) + return fail(fault, ESK_XT_LOAD, pc, addr, word); + if (bytes == 1) value &= 255u; + if (bytes == 2) value = signed16 ? sx(value & 65535u, 16) : value & 65535u; + next.a[t] = value; + } + } + *state = next; *consumed = width; return ES_OK; +} +es_status esk_xtensa_step(esk_xtensa_state *state, const esk_xtensa_memory *memory, + esk_xtensa_fault *fault) +{ + unsigned char code[3]; unsigned i, width = 2; uint32_t v; size_t used; + if (fault) memset(fault, 0, sizeof *fault); + if (!state) return ES_EINVAL; + for (i = 0; i < width; i++) { + uint32_t addr = state->pc + i; + if (!memory || !memory->read || memory->read(memory->ctx, addr, 1, &v) != ES_OK) + return fail(fault, ESK_XT_FETCH, state->pc, addr, 0); + code[i] = (unsigned char)(v & 255u); + if (i == 0 && (code[0] & 15u) < 8) width = 3; + } + return esk_xtensa_execute(state, memory, code, width, &used, fault); +} diff --git a/core/mcu/xtensa/execute.h b/core/mcu/xtensa/execute.h new file mode 100644 index 000000000..96ee68e44 --- /dev/null +++ b/core/mcu/xtensa/execute.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_XTENSA_EXECUTE_H +#define ESK_XTENSA_EXECUTE_H +#include "embsim/types.h" +/* Experimental non-windowed execution engine, not an ESP32 SoC/CPU backend. + * Virtual addresses equal bus addresses: no MMU, protection, caches, special + * registers, exceptions/interrupts, windows, timing or SMP. Faults are returned + * to the caller with PC/address/opcode, not dispatched to architectural vectors. + * Sequential callbacks provide ordering but make no multicore ordering claim. + * Callback failure MUST mean no completed write. No rollback of MMIO reads. */ +typedef struct esk_xtensa_state { uint32_t a[16], pc; } esk_xtensa_state; +typedef struct esk_xtensa_memory { + void *ctx; + es_status (*read)(void *ctx, uint32_t address, unsigned bytes, uint32_t *value); + es_status (*write)(void *ctx, uint32_t address, unsigned bytes, uint32_t value); +} esk_xtensa_memory; +enum { ESK_XT_OK, ESK_XT_UNSUPPORTED, ESK_XT_TRUNCATED, ESK_XT_FETCH, + ESK_XT_ALIGN, ESK_XT_LOAD, ESK_XT_STORE }; +typedef struct esk_xtensa_fault { unsigned kind; uint32_t pc, address, opcode; } esk_xtensa_fault; +/* Execute supplied instruction bytes; no state commit on failure. */ +es_status esk_xtensa_execute(esk_xtensa_state *state, const esk_xtensa_memory *memory, + const unsigned char *code, size_t available, + size_t *consumed, esk_xtensa_fault *fault); +/* Fetch exactly 2/3 bytes through byte reads, then execute one instruction. */ +es_status esk_xtensa_step(esk_xtensa_state *state, const esk_xtensa_memory *memory, + esk_xtensa_fault *fault); +#endif diff --git a/core/mcu/xtensa/integer.c b/core/mcu/xtensa/integer.c index af7fd11bc..f94194c97 100644 --- a/core/mcu/xtensa/integer.c +++ b/core/mcu/xtensa/integer.c @@ -17,6 +17,19 @@ es_status esk_xtensa_integer_execute(uint32_t a[16], const unsigned char *code, unsigned op, t, s, r, op1, op2, dest; if (consumed) *consumed = 0; if (!a || !code || !consumed) return ES_EINVAL; + if (available < 2) return ES_EINVAL; + insn = (uint32_t)code[0] | (uint32_t)code[1] << 8; + op = insn & 15u; t = (insn >> 4) & 15u; s = (insn >> 8) & 15u; r = insn >> 12; + if (op == 10 || op == 11 || (op == 12 && t < 8) || (op == 13 && r == 0)) { + if (op == 10) { dest = r; result = a[s] + a[t]; } + else if (op == 11) { dest = r; result = a[s] + (t ? (uint32_t)t : UINT32_MAX); } + else if (op == 12) { + uint32_t immediate = (uint32_t)t << 4 | r; + dest = s; result = immediate >= 96 ? immediate - 128u : immediate; + } else { dest = t; result = a[s]; } + a[dest] = result; *consumed = 2; return ES_OK; + } + if (op >= 8) return ES_EUNSUPPORTED; if (available < 3) return ES_EINVAL; insn = (uint32_t)code[0] | (uint32_t)code[1] << 8 | (uint32_t)code[2] << 16; op = insn & 15u; t = (insn >> 4) & 15u; s = (insn >> 8) & 15u; @@ -35,6 +48,20 @@ es_status esk_xtensa_integer_execute(uint32_t a[16], const unsigned char *code, result = op2 == 1 ? a[s] & a[t] : op2 == 2 ? a[s] | a[t] : a[s] ^ a[t]; } else if (op == 0 && op1 == 0 && op2 == 6 && s <= 1) { result = s == 0 || (a[t] & UINT32_C(0x80000000)) ? 0u - a[t] : a[t]; + } else if (op == 0 && op1 == 1 && op2 <= 1) { + unsigned shift = 32u - (op2 * 16u + t); + if (shift >= 32) return ES_EUNSUPPORTED; + result = a[s] << shift; + } else if (op == 0 && op1 == 1 && op2 == 4) { + result = a[t] >> s; + } else if (op == 0 && op1 == 1 && (op2 == 2 || op2 == 3)) { + unsigned shift = s + (op2 & 1u) * 16u; + result = a[t] >> shift; + if (shift && (a[t] & UINT32_C(0x80000000))) result |= UINT32_MAX << (32u - shift); + } else if (op == 0 && (op1 == 4 || op1 == 5)) { + unsigned shift = s + (op1 & 1u) * 16u; + if (shift + op2 + 1 > 32) return ES_EUNSUPPORTED; + result = (a[t] >> shift) & ((UINT32_C(1) << (op2 + 1)) - 1u); } else if (op == 0 && op1 == 2 && op2 == 8) { result = a[s] * a[t]; } else { diff --git a/core/mcu/xtensa/integer.h b/core/mcu/xtensa/integer.h index d9fe9fe2d..7228905d4 100644 --- a/core/mcu/xtensa/integer.h +++ b/core/mcu/xtensa/integer.h @@ -5,10 +5,12 @@ #include "embsim/types.h" /* Experimental LX6 little-endian integer execution kernel. The caller supplies * the currently visible A0..A15 window. This is NOT a CPU backend: no fetch, - * windows, interrupts/exceptions, memory, control flow, density, FPU, special + * windows, interrupts/exceptions, memory, control flow, FPU, special * registers, SMP or timing. Unsupported encodings never change the registers. - * 24-bit ADD/SUB/ADDX/SUBX, AND/OR/XOR, MOVI/ADDI/ADDMI, NEG/ABS and MULL only. - * No instruction timing is inferred. Success consumes three bytes. */ + * Wide ADD/SUB/ADDX/SUBX, AND/OR/XOR, MOVI/ADDI/ADDMI, NEG/ABS, MULL, + * immediate shifts and EXTUI. + * Also density ADD.N/ADDI.N/MOV.N/MOVI.N. No timing is inferred. + * Success consumes two or three bytes. */ es_status esk_xtensa_integer_execute(uint32_t a[16], const unsigned char *code, size_t available, size_t *consumed); #endif diff --git a/docs/STATUS.md b/docs/STATUS.md index 4a79409c9..a3b779387 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -17,9 +17,15 @@ Native `image-info esp32` now validates standalone image headers, segments, XOR checksum and optional SHA-256, with explicit rejection of signed/encrypted/merged images. An experimental, unregistered Xtensa integer kernel now executes the wide arithmetic/logic/immediate/low-multiply subset against a supplied register -window: 84 native assertions and 4,352 separately assembled all-register comparisons -pass. Fetch, control flow, memory, windows, interrupts, density, SMP and timing -remain unimplemented. No runnable ESP32 target is added, and the +window: 111 native assertions and 5,376 separately assembled all-register comparisons +pass. Mixed-width fetch, compact arithmetic, non-windowed calls/returns, conditional +branches and callback memory access now execute an assembled summing/subroutine +program. The execution test passes 618 assertions; a separate assembler/linker +referee passes 3,840 execution cases and 960 failed accesses. Architectural +exceptions, windows, interrupts, full ISA, SMP, SoC boot and timing remain +unimplemented. Additionally, 96 compiler-generated C executions (sum/sort/copy, +33,472 fetched instructions) pass buffer, guard, return and callee-save checks. +No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. ## The library's categories (2026-11-03) diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 0647a2e96..93ce853c6 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -34,20 +34,21 @@ can change. Vendor maximum ratings are not normal operating conditions. `core/mcu/xtensa/integer.c` executes a bounded subset of little-endian, wide (24-bit) instructions against a caller-provided A0–A15 register window: ADD, SUB, ADDX2/4/8, SUBX2/4/8, AND, OR, XOR, MOVI, ADDI, ADDMI, NEG, -ABS and MULL. MOV's OR encoding is included. Arithmetic wraps modulo 2^32, +ABS, MULL, SLLI/SRLI/SRAI and EXTUI. MOV's OR encoding is included. Arithmetic wraps modulo 2^32, including ABS/NEG of the most negative integer; sign extension uses unsigned arithmetic. Destinations may alias either source, and A0 is writable. Unsupported instructions and truncated/invalid inputs fail without changing any -register; successful execution reports three consumed bytes. This helper has no -fetch, memory operations, control flow, register-window rotation, interrupts, -exceptions, special registers, density instructions, FPU, SMP or timing model. +register; successful execution reports three consumed bytes. The integer helper also implements ADD.N, ADDI.N, MOV.N and MOVI.N; the +last uses Xtensa's asymmetric -32..95 immediate range. This helper has no +register-window rotation, interrupts, architectural exception dispatch, special +registers, FPU, SMP or timing model. It is not wired into the CPU constructor or catalog and cannot run firmware. The caller-provided window is not a claim that register-window behavior exists. `test_xtensa_integer` checks an assembled sequence, arithmetic extremes, aliases, -input errors and unchanged state on unsupported execution (84 assertions). -`tests/vendor/xtensa_integer/run.py` independently assembles 4,352 instruction +input errors and unchanged state on unsupported execution (111 assertions). +`tests/vendor/xtensa_integer/run.py` independently assembles 5,376 instruction cases with GNU binutils and compares every output register against Python integer arithmetic. It varies register aliases, immediates and 32-bit extremes with a fixed seed. GNU xtensa-lx106 binutils 2.40 was used solely as an external @@ -65,6 +66,70 @@ python tests/vendor/xtensa_integer/run.py \ The deterministic native unit test runs in CTest without those external tools. Hardware execution comparison and a complete LX6 ISA referee remain outstanding. +## Implemented: mixed-width fetch, flow and memory execution + +`core/mcu/xtensa/execute.c` adds a PC and a visible register window, plus a +byte/halfword/word callback bus. It executes: + +- The integer helper's wide and compact operations, NOP and NOP.N. +- J, JX, CALL0, CALLX0, RET and RET.N. CALLX0 reads the target before + writing A0, including when the target register is A0. Windowed calls and + returns remain explicitly unsupported. +- BEQZ/BNEZ/BLTZ/BGEZ, BEQ/BNE/BLT/BGE/BLTU/BGEU, BEQZ.N/BNEZ.N + and BEQI/BNEI/BLTI/BGEI/BLTUI/BGEUI with encoded constant selection. + Signed comparisons differ from unsigned; narrow branches have unsigned + forward displacement, while wide branches sign-extend their offsets. +- L8UI, L16UI, L16SI, L32I, S8I, S16I, S32I, L32I.N and S32I.N. + Narrow loads mask callback results, and signed halfword loads sign-extend. +- L32R in the default PC-relative mode; extended LITBASE is not implemented. + +Instruction fetch reads exactly two or three bytes, even at memory boundaries. +Data access checks natural alignment before calling the bus. On unsupported +instructions, truncation, failed fetch or failed data access, registers and PC +remain unchanged, and diagnostics retain the failing PC/address/opcode. These +are caller-visible fault reports, not architectural exception-vector dispatch. +The bus callback must not complete a write when reporting failure. Side effects +of MMIO reads cannot be rolled back. There is no MMU, protection, cache or +multicore ordering/timing claim. Execution is sequential and has no inferred +cycle count. + +An assembled mixed-width program fetches through memory, sums four words in a +loop, calls a doubling subroutine and returns to the expected PC (27 executed +instructions). The native test has 618 assertions covering branches, compact +immediate limits, byte/halfword/word behavior, literal loads, link-register alias, +32-bit PC wrap, memory-edge fetches, alignment and failed-access state retention. +An optional external-assembler/linker referee checks 3,840 execution cases and +960 rejected accesses against independent Python PC/register/bus expectations. +Linker relaxation is disabled to preserve the fixture's explicit instruction +addresses. The earlier 5,376 integer cases continue to pass. + +```sh +python tests/vendor/xtensa_integer/run_execute.py \ + --assembler /path/to/xtensa-lx106-elf-as \ + --objcopy /path/to/xtensa-lx106-elf-objcopy \ + --linker /path/to/xtensa-lx106-elf-ld +``` + +A further compiler acceptance runner compiles unchanged freestanding C for +summing words, insertion-sorting unsigned words and copying bytes. With GCC +xtensa-lx106 10.2.1 at `-O1`, 96 executions fetch 33,472 instructions and verify +results, complete output buffers/guards, source preservation, CALL0 callee-saved +registers and return addresses. This shared-subset compiler evidence is not LX6 +windowed-ABI, hardware or vendor SDK qualification. + +```sh +python tests/vendor/xtensa_integer/run_c.py \ + --compiler /path/to/xtensa-lx106-elf-gcc \ + --objcopy /path/to/xtensa-lx106-elf-objcopy \ + --linker /path/to/xtensa-lx106-elf-ld \ + --nm /path/to/xtensa-lx106-elf-nm +``` + +No CPU constructor, GDB register mapping or board catalog support is registered. +Full instruction coverage, windowed ABI, system registers, exception/interrupt +handling, synchronization, dual-core execution, SoC boot and peripherals still +need implementation. Single-step synthetic programs do not qualify ESP-IDF boot. + ## Implemented: standalone firmware integrity inspection `embsim image-info esp32 app.bin` inspects a plaintext original-ESP32 image diff --git a/tests/unit/test_xtensa_execute.c b/tests/unit/test_xtensa_execute.c new file mode 100644 index 000000000..e44a99941 --- /dev/null +++ b/tests/unit/test_xtensa_execute.c @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/xtensa/execute.h" + +typedef struct memory { unsigned char data[512]; unsigned reads, writes, fail_read, fail_write; } memory; +static es_status read_mem(void *ctx, uint32_t addr, unsigned bytes, uint32_t *value) +{ + memory *m = (memory *)ctx; unsigned i; + m->reads++; + if (m->fail_read || addr > sizeof m->data || bytes > sizeof m->data - addr) return ES_EINVAL; + *value = 0; + for (i = 0; i < bytes; i++) *value |= (uint32_t)m->data[addr+i] << (8*i); + return ES_OK; +} +static es_status write_mem(void *ctx, uint32_t addr, unsigned bytes, uint32_t value) +{ + memory *m = (memory *)ctx; unsigned i; + m->writes++; + if (m->fail_write || addr > sizeof m->data || bytes > sizeof m->data - addr) return ES_EINVAL; + for (i = 0; i < bytes; i++) m->data[addr+i] = (unsigned char)((value >> (8*i)) & 255u); + return ES_OK; +} +static es_status execute(esk_xtensa_state *s, esk_xtensa_memory *mem, uint32_t word, unsigned length, esk_xtensa_fault *f) +{ + unsigned char code[3]; size_t consumed = 99; es_status st; + code[0] = (unsigned char)(word & 255u); code[1] = (unsigned char)((word >> 8) & 255u); + code[2] = (unsigned char)((word >> 16) & 255u); + st = esk_xtensa_execute(s, mem, code, length, &consumed, f); + CHECK_EQ(consumed, st == ES_OK ? length : 0); + return st; +} +static void program(void) +{ + /* GNU binutils-assembled mixed-width summing loop and CALL0/RET.N. + * movi a2,256; movi.n a3,4; movi.n a4,0; + * loop: l32i.n a5,a2,0; add.n a4,a4,a5; addi.n a2,a2,4; + * addi.n a3,a3,-1; bnez a3,loop; call0 twice; j done; + * twice: add.n a4,a4,a4; ret.n; done: nop.n */ + static const unsigned char code[] = { + 0x22,0xa1,0, 0x0c,0x43, 0x0c,0x04, 0x58,0x02, 0x5a,0x44, + 0x4b,0x22, 0x0b,0x33, 0x56,0x43,0xff, 0x45,0,0, + 0xc6,0,0, 0x4a,0x44, 0x0d,0xf0, 0x3d,0xf0 + }; + memory m = {{0},0,0,0,0}; esk_xtensa_memory bus = {&m,read_mem,write_mem}; + esk_xtensa_state s = {{0},0}; esk_xtensa_fault f; unsigned i, steps = 0; + memcpy(m.data, code, sizeof code); + for (i = 0; i < 4; i++) m.data[256+i*4] = (unsigned char)(i+1); + while (s.pc != 28 && steps < 40) { CHECK_EQ(esk_xtensa_step(&s,&bus,&f), ES_OK); steps++; } + CHECK_EQ(steps,27); CHECK_EQ(s.pc,28); CHECK_EQ(s.a[4],20); CHECK_EQ(s.a[2],272); + CHECK_EQ(s.a[3],0); CHECK_EQ(s.a[0],21); CHECK_EQ(m.writes,0); + CHECK_EQ(esk_xtensa_step(&s,&bus,&f),ES_OK); CHECK_EQ(s.pc,30); +} +static void memory_and_faults(void) +{ + memory m = {{0},0,0,0,0}; esk_xtensa_memory bus = {&m,read_mem,write_mem}; + esk_xtensa_state s = {{0},0}, before; esk_xtensa_fault f; + s.a[2]=UINT32_C(0x89abcdef); s.a[3]=256; + CHECK_EQ(execute(&s,&bus,0x006322,3,&f),ES_OK); /* s32i a2,a3,0 */ + CHECK_EQ(m.data[256],0xef); CHECK_EQ(m.data[259],0x89); + s.a[2]=0; CHECK_EQ(execute(&s,&bus,0x002322,3,&f),ES_OK); CHECK_EQ(s.a[2],UINT32_C(0x89abcdef)); + CHECK_EQ(execute(&s,&bus,0x009322,3,&f),ES_OK); CHECK_EQ(s.a[2],UINT32_C(0xffffcdef)); + CHECK_EQ(execute(&s,&bus,0x001322,3,&f),ES_OK); CHECK_EQ(s.a[2],0xcdef); + CHECK_EQ(execute(&s,&bus,0x000322,3,&f),ES_OK); CHECK_EQ(s.a[2],0xef); + s.a[2]=UINT32_C(0x12345678); CHECK_EQ(execute(&s,&bus,0x004322,3,&f),ES_OK); + CHECK_EQ(m.data[256],0x78); CHECK_EQ(m.data[257],0xcd); + CHECK_EQ(execute(&s,&bus,0x005322,3,&f),ES_OK); CHECK_EQ(m.data[257],0x56); CHECK_EQ(m.data[258],0xab); + /* A failed/unaligned store changes neither state nor memory. */ + s.a[3]=257; before=s; { unsigned writes=m.writes; + CHECK_EQ(execute(&s,&bus,0x006322,3,&f),ES_EINVAL); + CHECK_EQ(f.kind,ESK_XT_ALIGN); CHECK_EQ(f.address,257); CHECK_EQ(f.pc,before.pc); + CHECK_EQ(memcmp(&s,&before,sizeof s),0); CHECK_EQ(m.writes,writes); + } + s.a[3]=256; before=s; m.fail_write=1; + CHECK_EQ(execute(&s,&bus,0x006322,3,&f),ES_EINVAL); CHECK_EQ(f.kind,ESK_XT_STORE); + CHECK_EQ(memcmp(&s,&before,sizeof s),0); CHECK_EQ(m.data[256],0x78); + m.fail_read=1; CHECK_EQ(execute(&s,&bus,0x002322,3,&f),ES_EINVAL); + CHECK_EQ(f.kind,ESK_XT_LOAD); CHECK_EQ(memcmp(&s,&before,sizeof s),0); + m.fail_read=0; m.fail_write=0; + CHECK_EQ(execute(&s,&bus,0,3,&f),ES_EUNSUPPORTED); CHECK_EQ(f.kind,ESK_XT_UNSUPPORTED); + CHECK_EQ(memcmp(&s,&before,sizeof s),0); + /* Fetch compact instruction at the memory edge, without a third read. */ + m.data[510]=0x3d; m.data[511]=0xf0; s.pc=510; + CHECK_EQ(esk_xtensa_step(&s,&bus,&f),ES_OK); CHECK_EQ(s.pc,512); + before=s; CHECK_EQ(esk_xtensa_step(&s,&bus,&f),ES_EINVAL); + CHECK_EQ(f.kind,ESK_XT_FETCH); CHECK_EQ(f.address,512); CHECK_EQ(memcmp(&s,&before,sizeof s),0); + /* CALLX0 a0 reads the old link register before replacing it. */ + s.pc=7; s.a[0]=100; CHECK_EQ(execute(&s,&bus,0x0000c0,3,&f),ES_OK); + CHECK_EQ(s.pc,100); CHECK_EQ(s.a[0],10); + /* L32R uses aligned PC+3 and a one-extended 16-bit displacement. */ + s.pc=260; m.data[256]=0x91; + CHECK_EQ(execute(&s,&bus,0xffff21,3,&f),ES_OK); CHECK_EQ(s.a[2],UINT32_C(0x89ab5691)); + s.pc=UINT32_C(0xfffffffe); + CHECK_EQ(execute(&s,&bus,0x000006,3,&f),ES_OK); CHECK_EQ(s.pc,2); +} +static void branches_and_density(void) +{ + /* Assembled branches to 0x60; each pair specifies taken/not-taken inputs. */ + static const struct branch { uint32_t code, pc, yes_s, yes_t, no_s, no_t; unsigned bytes; } table[] = { + {0x024216,0x38,0,0,1,0,3}, {0x021356,0x3b,1,0,0,0,3}, + {0x01e496,0x3e,0x80000000u,0,0,0,3}, {0x01b5d6,0x41,0,0,0xffffffffu,0,3}, + {0x181237,0x44,7,7,7,8,3}, {0x159237,0x47,7,8,7,7,3}, + {0x122237,0x4a,0x80000000u,0,0,0x80000000u,3}, + {0x0fa237,0x4d,0,0x80000000u,0x80000000u,0,3}, + {0x0c3237,0x50,0,0xffffffffu,0xffffffffu,0,3}, + {0x09b237,0x53,0xffffffffu,0,0,0xffffffffu,3}, + {0x628c,0x56,0,0,1,0,2}, {0x43cc,0x58,1,0,0,0,2} + }; + size_t i; unsigned taken; + for (i=0; icode>>8)&15u; + s.pc=b->pc; s.a[reg]=taken ? b->yes_s : b->no_s; + if ((b->code&15u)==7) s.a[(b->code>>4)&15u]=taken ? b->yes_t : b->no_t; + CHECK_EQ(execute(&s,NULL,b->code,b->bytes,&f),ES_OK); + CHECK_EQ(s.pc,taken ? 0x60 : b->pc+b->bytes); CHECK_EQ(f.kind,ESK_XT_OK); + } + } + /* The MOVI.N range is asymmetric: -32..95, not -64..63. */ + { int value; + for (value=-32; value<=95; value++) { + esk_xtensa_state s={{0},0}; esk_xtensa_fault f; + uint32_t imm=(uint32_t)value&127u; + uint32_t code=12u | (imm>>4)<<4 | 2u<<8 | (imm&15u)<<12; + CHECK_EQ(execute(&s,NULL,code,2,&f),ES_OK); CHECK_EQ(s.a[2],(uint32_t)value); + } + } + /* Calls using windows are not silently interpreted as CALL0. */ + { esk_xtensa_state s={{0},0}, before=s; esk_xtensa_fault f; + CHECK_EQ(execute(&s,NULL,0x000015,3,&f),ES_EUNSUPPORTED); + CHECK_EQ(memcmp(&s,&before,sizeof s),0); + CHECK_EQ(execute(&s,NULL,0xf01d,2,&f),ES_EUNSUPPORTED); /* RETW.N */ + CHECK_EQ(memcmp(&s,&before,sizeof s),0); + } +} +static void branch_immediates(void) +{ + static const struct { uint32_t code, yes, no; } cases[] = { + {0x041226,1,0}, {0x040266,0,UINT32_MAX}, + {0x0402a6,0x80000000u,0}, {0x04f2e6,256,255}, + {0x0402b6,32767,32768}, {0x0412f6,65536,65535} + }; + size_t i; unsigned taken; + for (i=0;i> shift if operation == 'srli' else (x if x < 2**31 else x-2**32) >> shift + elif operation == 'extui': + shift = rng.randrange(32); bits = rng.randrange(1,min(16,32-shift)+1) + instruction = f'extui a{d},a{s},{shift},{bits}'; result = (x >> shift) & ((1 << bits)-1) elif operation == 'movi': immediate = rng.randrange(-2048, 2048) instruction = f'movi a{d},{immediate}'; result = immediate diff --git a/tests/vendor/xtensa_integer/run_c.py b/tests/vendor/xtensa_integer/run_c.py new file mode 100644 index 000000000..a9e32689b --- /dev/null +++ b/tests/vendor/xtensa_integer/run_c.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""Compile unchanged freestanding C routines and run them through fetch/bus execution. + +External LX106 CALL0 GCC tests the shared instruction subset, not ESP32 boot, +LX6 register windows, SMP, cycle accuracy or vendor SDK compatibility. +""" +import argparse +import ctypes as C +from pathlib import Path +import random +import subprocess +import tempfile +from run_execute import State, Fault, Read, Write, Memory, ROOT, MASK + +SOURCE = ''' +unsigned sum(const unsigned *p, unsigned n) { + unsigned result=0; + while(n--) result+=*p++; + return result; +} +void insertion_sort(unsigned *p, unsigned n) { + for (unsigned i=1;iv) { p[j]=p[j-1]; --j; } + p[j]=v; + } +} +void copy_bytes(unsigned char *out, const unsigned char *in, unsigned n) { + for (unsigned i=0;i= 0) + if taken: next_pc = target + elif operation in ['beq', 'bne', 'blt', 'bge', 'bltu', 'bgeu']: + asm += f' a{s},a{t},.L{i}{target-pc:+d}' + x, y = regs[s], regs[t] + taken = {'beq': x == y, 'bne': x != y, 'blt': signed(x) < signed(y), + 'bge': signed(x) >= signed(y), 'bltu': x < y, 'bgeu': x >= y}[operation] + if taken: next_pc = target + elif operation in ['beqi','bnei','blti','bgei','bltui','bgeui']: + unsigned = operation in ['bltui','bgeui'] + values = ([32768,65536] if unsigned else [-1,1])+[2,3,4,5,6,7,8,10,12,16,32,64,128,256] + immediate = values[k % 16] + regs[s] = (immediate + [0,-1,1][k % 3]) & MASK; expected = list(regs) + asm += f' a{s},{immediate},.L{i}{target-pc:+d}' + x = regs[s] if unsigned else signed(regs[s]) + taken = x == immediate if operation == 'beqi' else x != immediate if operation == 'bnei' else x < immediate if operation in ['blti','bltui'] else x >= immediate + if taken: next_pc = target + elif operation == 'l32r': + asm += f' a{d},.L{i}-4'; value = rng.getrandbits(32) + expected[d] = value; access = ('r', (pc-4) & MASK, 4, value) + elif operation.startswith(('l8', 'l16', 'l32', 's8', 's16', 's32')): + size = 1 if '8' in operation else 2 if '16' in operation else 4 + offset = rng.randrange(16 if operation.endswith('.n') else 256)*size + regs[s] = rng.getrandbits(32) & 0xfffffffc + expected = list(regs) + address = (regs[s]+offset) & MASK + asm += f' a{d},a{s},{offset}' + if operation.startswith('l'): + value = rng.getrandbits(32) + loaded = value & ((1 << (8*size))-1) + if operation == 'l16si' and loaded >= 32768: loaded -= 65536 + expected[d] = loaded & MASK + access = ('r', address, size, value) + else: access = ('w', address, size, regs[d]) + elif operation == 'movi.n': + value = rng.randrange(-32,96); asm += f' a{d},{value}'; expected[d] = value & MASK + elif operation == 'mov.n': asm += f' a{d},a{s}'; expected[d] = regs[s] + elif operation == 'add.n': asm += f' a{d},a{s},a{t}'; expected[d] = (regs[s]+regs[t]) & MASK + elif operation == 'addi.n': + value = rng.choice([-1]+list(range(1,16))); asm += f' a{d},a{s},{value}'; expected[d] = (regs[s]+value) & MASK + cases.append((asm, regs, expected, pc, next_pc & MASK, width, access)) + with tempfile.TemporaryDirectory(prefix='embsim-xtensa-execute-') as tmp: + out = Path(tmp); source=out/'execute.s'; obj=out/'execute.o'; elf=out/'execute.elf'; binary=out/'execute.bin' + source.write_text('.text\n.global _start\n_start:\n.begin no-transform\n'+'\n'.join( + f'.org {i*16}\n.L{i}:\n{case[0]}' for i, case in enumerate(cases))+'\n.end no-transform\n',encoding='utf-8') + subprocess.run([args.assembler,str(source),'-o',str(obj)],check=True) + subprocess.run([args.linker,'--no-relax','-Ttext=0x1000',str(obj),'-o',str(elf)],check=True) + subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(elf),str(binary)],check=True) + data=binary.read_bytes();lib=out/'execute.so' + subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC', + '-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),str(ROOT/'core/mcu/xtensa/integer.c'), + str(ROOT/'core/mcu/xtensa/execute.c'),'-o',str(lib)],check=True) + execute=C.CDLL(str(lib)).esk_xtensa_execute + execute.argtypes=[C.POINTER(State),C.POINTER(Memory),C.POINTER(C.c_ubyte),C.c_size_t,C.POINTER(C.c_size_t),C.POINTER(Fault)] + execute.restype=C.c_int + fault_checks=0 + for i,(asm,regs,expected,pc,next_pc,width,access) in enumerate(cases): + for refuse in ([False,True] if access else [False]): + state=State((C.c_uint32*16)(*regs),pc); calls=[] + @Read + def read(ctx,address,size,value): + calls.append(('r',address,size)); value[0]=access[3] if access else 0 + return -1 if refuse else 0 + @Write + def write(ctx,address,size,value): + calls.append(('w',address,size,value)); return -1 if refuse else 0 + memory=Memory(None,read,write);used=C.c_size_t();fault=Fault() + code=(C.c_ubyte*width).from_buffer_copy(data[i*16:i*16+width]) + status=execute(C.byref(state),C.byref(memory),code,width,C.byref(used),C.byref(fault)) + if refuse: + assert status != 0 and used.value == 0 and state.pc == pc and list(state.a) == regs,(asm,status,fault.kind) + assert fault.pc == pc and fault.address == access[1] and fault.kind == (5 if access[0]=='r' else 6),asm + fault_checks+=1 + else: + assert status == 0 and used.value == width,(asm,status,fault.kind) + assert list(state.a) == expected and state.pc == next_pc,(asm,bytes(code).hex(),list(state.a),expected,hex(state.pc),hex(next_pc)) + if access: + wanted=access[:3] if access[0]=='r' else access + assert calls == [wanted],(asm,calls,wanted) + else: assert not calls,asm + print(f'{len(cases)} independently assembled execution cases and {fault_checks} rejected accesses pass') + + +if __name__ == '__main__': main() From 7dd2847d670a4e37a67b7b85eb594a605eddd2f1 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Wed, 4 Nov 2026 21:31:26 +0000 Subject: [PATCH 06/57] Keep branch-PC test expectations unsigned for MSVC --- tests/unit/test_xtensa_execute.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_xtensa_execute.c b/tests/unit/test_xtensa_execute.c index e44a99941..84e2df09e 100644 --- a/tests/unit/test_xtensa_execute.c +++ b/tests/unit/test_xtensa_execute.c @@ -147,7 +147,7 @@ static void branch_immediates(void) esk_xtensa_state s={{0},100}; esk_xtensa_fault f; s.a[2]=taken ? cases[i].yes : cases[i].no; CHECK_EQ(execute(&s,NULL,cases[i].code,3,&f),ES_OK); - CHECK_EQ(s.pc,taken ? 108 : 103); + CHECK_EQ(s.pc,taken ? 108u : 103u); } } TEST_MAIN_BEGIN From a1ffd448a9ec0e8e5cbacf72f85c653871fd07e8 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Wed, 4 Nov 2026 21:47:46 +0000 Subject: [PATCH 07/57] Execute Xtensa register windows and hardware loops with real ESP32 spill/fill acceptance --- CHANGELOG.md | 15 ++ core/mcu/xtensa/execute.c | 44 ++-- core/mcu/xtensa/execute.h | 5 + core/mcu/xtensa/integer.c | 48 +++-- core/mcu/xtensa/integer.h | 2 + core/mcu/xtensa/windows.c | 199 +++++++++++++++++++ core/mcu/xtensa/windows.h | 29 +++ docs/STATUS.md | 13 +- docs/engineering/esp32-dual-core.md | 100 +++++++++- tests/unit/test_xtensa_windows.c | 156 +++++++++++++++ tests/vendor/README.md | 13 ++ tests/vendor/xtensa_integer/run_windows_c.py | 133 +++++++++++++ 12 files changed, 720 insertions(+), 37 deletions(-) create mode 100644 core/mcu/xtensa/windows.c create mode 100644 core/mcu/xtensa/windows.h create mode 100644 tests/unit/test_xtensa_windows.c create mode 100644 tests/vendor/xtensa_integer/run_windows_c.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c3779f94..fe6fee8b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental Xtensa windows and hardware loops + +- Implemented 64 physical address registers, overlapping windows, CALL4/8/12 + and indirect calls, ENTRY, RETW, lazy overflow/underflow vector transitions, + RFWO/RFWU retries, exception-mode spill/fill loads/stores and selected special + registers. Unsupported privilege/general exception handling remains explicit. +- Added hardware loops with sequential-versus-taken flow tracking and EXCM + suppression. Initial state/vectors are configured test inputs, not ESP32 reset + defaults or a qualified SoC memory map. +- Added 1,337 native assertions and real ESP32 windowed-ABI acceptance: 224 + executions (160 compiled C, 64 assembly) run the actual ESP-IDF spill/fill + instruction bodies, exercising all six window exception paths through memory. + This does not implement general interrupts, SMP, SoC boot or the board yet. +- Fixed an MSVC signed/unsigned mismatch in a branch-PC test expectation. + ### Experimental Xtensa fetch, flow and memory execution - Execute mixed-width instructions through a callback bus, compact arithmetic, diff --git a/core/mcu/xtensa/execute.c b/core/mcu/xtensa/execute.c index bc55e418d..0241a4479 100644 --- a/core/mcu/xtensa/execute.c +++ b/core/mcu/xtensa/execute.c @@ -14,17 +14,18 @@ static es_status fail(esk_xtensa_fault *f, unsigned kind, uint32_t pc, uint32_t if (f) { f->kind = kind; f->pc = pc; f->address = addr; f->opcode = opcode; } return kind == ESK_XT_UNSUPPORTED ? ES_EUNSUPPORTED : ES_EINVAL; } -es_status esk_xtensa_execute(esk_xtensa_state *state, const esk_xtensa_memory *memory, - const unsigned char *code, size_t available, - size_t *consumed, esk_xtensa_fault *fault) +static es_status execute_inner(esk_xtensa_state *state, const esk_xtensa_memory *memory, + const unsigned char *code, size_t available, + size_t *consumed, esk_xtensa_fault *fault, unsigned *sequential) { esk_xtensa_state next; uint32_t word, pc, addr = 0, value = 0; - unsigned op, t, s, r, width, bytes = 0; + unsigned op, t, s, r, width, bytes = 0, flow = 1; int store = 0, signed16 = 0; size_t used = 0; es_status st; if (consumed) *consumed = 0; + if (sequential) *sequential = 0; if (fault) memset(fault, 0, sizeof *fault); if (!state || !code || !consumed) return ES_EINVAL; pc = state->pc; @@ -38,18 +39,18 @@ es_status esk_xtensa_execute(esk_xtensa_state *state, const esk_xtensa_memory *m st = esk_xtensa_integer_execute(next.a, code, available, &used); if (st == ES_OK) { next.pc = pc + (uint32_t)used; width = (unsigned)used; } else if (word == 0x0020f0 || word == 0xf03d) { /* NOP / NOP.N */ } - else if (word == 0x000080 || word == 0xf00d) next.pc = next.a[0]; /* RET/RET.N */ - else if ((word & UINT32_C(0xfff0ff)) == 0x0000a0) next.pc = next.a[s]; /* JX */ + else if (word == 0x000080 || word == 0xf00d) { next.pc = next.a[0]; flow = 0; } /* RET/RET.N */ + else if ((word & UINT32_C(0xfff0ff)) == 0x0000a0) { next.pc = next.a[s]; flow = 0; } /* JX */ else if ((word & UINT32_C(0xfff0ff)) == 0x0000c0) { /* CALLX0: read before A0 write */ - next.pc = next.a[s]; next.a[0] = pc + 3; + next.pc = next.a[s]; next.a[0] = pc + 3; flow = 0; } else if ((word & 63u) == 5) { /* CALL0 */ next.pc = (pc & UINT32_C(0xfffffffc)) + 4 + (sx(word >> 6, 18) << 2); - next.a[0] = pc + 3; - } else if ((word & 63u) == 6) next.pc = pc + 4 + sx(word >> 6, 18); /* J */ + next.a[0] = pc + 3; flow = 0; + } else if ((word & 63u) == 6) { next.pc = pc + 4 + sx(word >> 6, 18); flow = 0; } /* J */ else if (op == 6 && (t & 3u) == 1) { int taken = t == 1 ? next.a[s] == 0 : t == 5 ? next.a[s] != 0 : t == 9 ? (next.a[s] >> 31) != 0 : (next.a[s] >> 31) == 0; - if (taken) next.pc = pc + 4 + sx(word >> 12, 12); + if (taken) { next.pc = pc + 4 + sx(word >> 12, 12); flow = 0; } } else if (op == 6 && (t == 2 || t == 6 || t == 10 || t == 14 || t == 11 || t == 15)) { static const uint32_t constants[16] = { UINT32_MAX,1,2,3,4,5,6,7,8,10,12,16,32,64,128,256 @@ -59,16 +60,16 @@ es_status esk_xtensa_execute(esk_xtensa_state *state, const esk_xtensa_memory *m if (t == 10 || t == 14) { left ^= UINT32_C(0x80000000); right ^= UINT32_C(0x80000000); } taken = t == 2 ? left == right : t == 6 ? left != right : (t == 10 || t == 11) ? left < right : left >= right; - if (taken) next.pc = pc + 4 + sx(word >> 16, 8); + if (taken) { next.pc = pc + 4 + sx(word >> 16, 8); flow = 0; } } else if (op == 7 && (r == 1 || r == 9 || r == 2 || r == 10 || r == 3 || r == 11)) { uint32_t left = next.a[s], right = next.a[t]; int taken; if (r == 2 || r == 10) { left ^= UINT32_C(0x80000000); right ^= UINT32_C(0x80000000); } taken = r == 1 ? left == right : r == 9 ? left != right : (r == 2 || r == 3) ? left < right : left >= right; - if (taken) next.pc = pc + 4 + sx(word >> 16, 8); + if (taken) { next.pc = pc + 4 + sx(word >> 16, 8); flow = 0; } } else if (op == 12 && t >= 8) { int taken = t < 12 ? next.a[s] == 0 : next.a[s] != 0; - if (taken) next.pc = pc + 4 + ((t & 3u) << 4 | r); + if (taken) { next.pc = pc + 4 + ((t & 3u) << 4 | r); flow = 0; } } else if (op == 8 || op == 9) { bytes = 4; addr = next.a[s] + (uint32_t)r * 4; store = op == 9; } else if (op == 2 && (r <= 2 || r == 4 || r == 5 || r == 6 || r == 9)) { @@ -91,7 +92,22 @@ es_status esk_xtensa_execute(esk_xtensa_state *state, const esk_xtensa_memory *m next.a[t] = value; } } - *state = next; *consumed = width; return ES_OK; + *state = next; *consumed = width; + if (sequential) *sequential = flow; + return ES_OK; +} +es_status esk_xtensa_execute(esk_xtensa_state *state, const esk_xtensa_memory *memory, + const unsigned char *code, size_t available, + size_t *consumed, esk_xtensa_fault *fault) +{ + return execute_inner(state,memory,code,available,consumed,fault,NULL); +} +es_status esk_xtensa_execute_flow(esk_xtensa_state *state, const esk_xtensa_memory *memory, + const unsigned char *code, size_t available, + size_t *consumed, esk_xtensa_fault *fault, unsigned *sequential) +{ + if (!sequential) return ES_EINVAL; + return execute_inner(state,memory,code,available,consumed,fault,sequential); } es_status esk_xtensa_step(esk_xtensa_state *state, const esk_xtensa_memory *memory, esk_xtensa_fault *fault) diff --git a/core/mcu/xtensa/execute.h b/core/mcu/xtensa/execute.h index 96ee68e44..222172f00 100644 --- a/core/mcu/xtensa/execute.h +++ b/core/mcu/xtensa/execute.h @@ -22,6 +22,11 @@ typedef struct esk_xtensa_fault { unsigned kind; uint32_t pc, address, opcode; } es_status esk_xtensa_execute(esk_xtensa_state *state, const esk_xtensa_memory *memory, const unsigned char *code, size_t available, size_t *consumed, esk_xtensa_fault *fault); +/* As execute, also reports sequential increment (not a taken branch/jump/call), + * for hardware loop-end handling. A target equal to PC+width is still taken. */ +es_status esk_xtensa_execute_flow(esk_xtensa_state *state, const esk_xtensa_memory *memory, + const unsigned char *code, size_t available, + size_t *consumed, esk_xtensa_fault *fault, unsigned *sequential); /* Fetch exactly 2/3 bytes through byte reads, then execute one instruction. */ es_status esk_xtensa_step(esk_xtensa_state *state, const esk_xtensa_memory *memory, esk_xtensa_fault *fault); diff --git a/core/mcu/xtensa/integer.c b/core/mcu/xtensa/integer.c index f94194c97..ec2a7b466 100644 --- a/core/mcu/xtensa/integer.c +++ b/core/mcu/xtensa/integer.c @@ -10,10 +10,10 @@ static uint32_t extend(uint32_t x, unsigned bits) uint32_t sign = UINT32_C(1) << (bits - 1); return (x ^ sign) - sign; } -es_status esk_xtensa_integer_execute(uint32_t a[16], const unsigned char *code, - size_t available, size_t *consumed) +static es_status execute_inner(uint32_t a[16], const unsigned char *code, + size_t available, size_t *consumed, uint32_t *references) { - uint32_t insn, result; + uint32_t insn, result, mask = 0; unsigned op, t, s, r, op1, op2, dest; if (consumed) *consumed = 0; if (!a || !code || !consumed) return ES_EINVAL; @@ -21,13 +21,15 @@ es_status esk_xtensa_integer_execute(uint32_t a[16], const unsigned char *code, insn = (uint32_t)code[0] | (uint32_t)code[1] << 8; op = insn & 15u; t = (insn >> 4) & 15u; s = (insn >> 8) & 15u; r = insn >> 12; if (op == 10 || op == 11 || (op == 12 && t < 8) || (op == 13 && r == 0)) { - if (op == 10) { dest = r; result = a[s] + a[t]; } - else if (op == 11) { dest = r; result = a[s] + (t ? (uint32_t)t : UINT32_MAX); } + if (op == 10) { dest = r; result = a[s] + a[t]; mask = (1u << s) | (1u << t); } + else if (op == 11) { dest = r; result = a[s] + (t ? (uint32_t)t : UINT32_MAX); mask = 1u << s; } else if (op == 12) { uint32_t immediate = (uint32_t)t << 4 | r; dest = s; result = immediate >= 96 ? immediate - 128u : immediate; - } else { dest = t; result = a[s]; } - a[dest] = result; *consumed = 2; return ES_OK; + } else { dest = t; result = a[s]; mask = 1u << s; } + a[dest] = result; *consumed = 2; + if (references) *references = mask | (1u << dest); + return ES_OK; } if (op >= 8) return ES_EUNSUPPORTED; if (available < 3) return ES_EINVAL; @@ -39,34 +41,48 @@ es_status esk_xtensa_integer_execute(uint32_t a[16], const unsigned char *code, dest = t; result = extend((insn >> 16) | (uint32_t)s << 8, 12); } else if (op == 2 && (r == 12 || r == 13)) { uint32_t immediate = extend(insn >> 16, 8); - dest = t; result = a[s] + (r == 13 ? immediate << 8 : immediate); + dest = t; result = a[s] + (r == 13 ? immediate << 8 : immediate); mask = 1u << s; } else if (op == 0 && op1 == 0 && op2 >= 8) { unsigned shift = op2 & 3u; uint32_t left = a[s] << shift; - result = op2 < 12 ? left + a[t] : left - a[t]; + result = op2 < 12 ? left + a[t] : left - a[t]; mask = (1u << s) | (1u << t); } else if (op == 0 && op1 == 0 && op2 >= 1 && op2 <= 3) { - result = op2 == 1 ? a[s] & a[t] : op2 == 2 ? a[s] | a[t] : a[s] ^ a[t]; + result = op2 == 1 ? a[s] & a[t] : op2 == 2 ? a[s] | a[t] : a[s] ^ a[t]; mask = (1u << s) | (1u << t); } else if (op == 0 && op1 == 0 && op2 == 6 && s <= 1) { - result = s == 0 || (a[t] & UINT32_C(0x80000000)) ? 0u - a[t] : a[t]; + result = s == 0 || (a[t] & UINT32_C(0x80000000)) ? 0u - a[t] : a[t]; mask = 1u << t; } else if (op == 0 && op1 == 1 && op2 <= 1) { unsigned shift = 32u - (op2 * 16u + t); if (shift >= 32) return ES_EUNSUPPORTED; - result = a[s] << shift; + result = a[s] << shift; mask = 1u << s; } else if (op == 0 && op1 == 1 && op2 == 4) { - result = a[t] >> s; + result = a[t] >> s; mask = 1u << t; } else if (op == 0 && op1 == 1 && (op2 == 2 || op2 == 3)) { unsigned shift = s + (op2 & 1u) * 16u; - result = a[t] >> shift; + result = a[t] >> shift; mask = 1u << t; if (shift && (a[t] & UINT32_C(0x80000000))) result |= UINT32_MAX << (32u - shift); } else if (op == 0 && (op1 == 4 || op1 == 5)) { unsigned shift = s + (op1 & 1u) * 16u; if (shift + op2 + 1 > 32) return ES_EUNSUPPORTED; - result = (a[t] >> shift) & ((UINT32_C(1) << (op2 + 1)) - 1u); + result = (a[t] >> shift) & ((UINT32_C(1) << (op2 + 1)) - 1u); mask = 1u << t; } else if (op == 0 && op1 == 2 && op2 == 8) { - result = a[s] * a[t]; + result = a[s] * a[t]; mask = (1u << s) | (1u << t); } else { return ES_EUNSUPPORTED; } a[dest] = result; *consumed = 3; + if (references) *references = mask | (1u << dest); return ES_OK; } + +es_status esk_xtensa_integer_execute(uint32_t a[16], const unsigned char *code, + size_t available, size_t *consumed) +{ + return execute_inner(a, code, available, consumed, NULL); +} +es_status esk_xtensa_integer_references(const unsigned char *code, size_t available, uint32_t *mask) +{ + uint32_t a[16] = {0}; size_t consumed; + if (!mask) return ES_EINVAL; + *mask = 0; + return execute_inner(a, code, available, &consumed, mask); +} diff --git a/core/mcu/xtensa/integer.h b/core/mcu/xtensa/integer.h index 7228905d4..3b1e7cb4d 100644 --- a/core/mcu/xtensa/integer.h +++ b/core/mcu/xtensa/integer.h @@ -13,4 +13,6 @@ * Success consumes two or three bytes. */ es_status esk_xtensa_integer_execute(uint32_t a[16], const unsigned char *code, size_t available, size_t *consumed); +/* Exact read/write register references for the supported integer encodings. */ +es_status esk_xtensa_integer_references(const unsigned char *code, size_t available, uint32_t *mask); #endif diff --git a/core/mcu/xtensa/windows.c b/core/mcu/xtensa/windows.c new file mode 100644 index 000000000..19b4f15f4 --- /dev/null +++ b/core/mcu/xtensa/windows.c @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "mcu/xtensa/windows.h" +#include "mcu/xtensa/integer.h" +#include + +static uint32_t bit(unsigned n) { return UINT32_C(1) << (n & 15u); } +static void save(esk_xtensa_windows *w) +{ + unsigned i; for (i=0;i<16;i++) w->ar[(w->base*4+i)&63u]=w->cpu.a[i]; +} +static void load(esk_xtensa_windows *w) +{ + unsigned i; for (i=0;i<16;i++) w->cpu.a[i]=w->ar[(w->base*4+i)&63u]; +} +static es_status reject(esk_xtensa_fault *f, unsigned kind, uint32_t pc, uint32_t addr, uint32_t op) +{ + if (f) { f->kind=kind; f->pc=pc; f->address=addr; f->opcode=op; } + return kind==ESK_XT_UNSUPPORTED ? ES_EUNSUPPORTED : ES_EINVAL; +} +void esk_xtensa_windows_init(esk_xtensa_windows *w, uint32_t pc, const uint32_t vectors[6]) +{ + if (!w) return; + memset(w,0,sizeof *w); w->cpu.pc=pc; w->ps=ESK_XT_PS_WOE; w->start=1; + if (vectors) memcpy(w->vectors,vectors,sizeof w->vectors); +} +/* Register references are checked before any access. Zero/compare branches do + * not reference their instruction fields as destination register numbers. */ +static es_status references(const unsigned char *code, unsigned width, uint32_t word, uint32_t *mask) +{ + unsigned op=word&15u,t=(word>>4)&15u,s=(word>>8)&15u,r=(word>>12)&15u; + if (esk_xtensa_integer_references(code,width,mask)==ES_OK) return ES_OK; + *mask=0; + if (word==0x0020f0 || word==0xf03d) return ES_OK; + if (word==0x000080 || word==0xf00d) *mask=1; + else if ((word&UINT32_C(0xfff0ff))==0x0000a0) *mask=bit(s); + else if ((word&UINT32_C(0xfff0ff))==0x0000c0) *mask=bit(s)|1u; + else if ((word&63u)==5) *mask=1; + else if ((word&63u)==6) return ES_OK; + else if (op==6 && ((t&3u)==1 || t==2 || t==6 || t==10 || t==14 || t==11 || t==15)) *mask=bit(s); + else if (op==7 && (r==1 || r==9 || r==2 || r==10 || r==3 || r==11)) *mask=bit(s)|bit(t); + else if (op==12 && t>=8) *mask=bit(s); + else if (op==8 || op==9 || (op==2 && (r<=2 || r==4 || r==5 || r==6 || r==9))) *mask=bit(s)|bit(t); + else if (op==1) *mask=bit(t); + else return ES_EUNSUPPORTED; + return ES_OK; +} +/* Lazy overflow: only the actually referenced register groups matter. */ +static unsigned overflow(esk_xtensa_windows *w, uint32_t mask) +{ + unsigned max=0,i,n=0,victim,event; + if (!(w->ps&ESK_XT_PS_WOE) || (w->ps&ESK_XT_PS_EXCM)) return 0; + for (i=4;i<16;i++) if (mask&(1u<start&bit(w->base+i)) { n=i; break; } + if (!n) return 0; + victim=(w->base+n)&15u; + event=(w->start&bit(victim+1)) ? 1u : (w->start&bit(victim+2)) ? 3u : 5u; + w->epc1=w->cpu.pc; + w->ps=(w->ps&~UINT32_C(0xf00)) | (w->base<<8) | ESK_XT_PS_EXCM; + save(w); w->base=victim; load(w); w->cpu.pc=w->vectors[event-1]; + return event; +} +es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memory *memory, + const unsigned char *code, size_t available, + size_t *consumed, unsigned *event, esk_xtensa_fault *fault) +{ + esk_xtensa_windows next; + uint32_t word,pc,mask=0,target=0; unsigned width,op,t,s,n=0,kind=0,e,flow=1; + es_status st; + if (consumed) *consumed=0; + if (event) *event=0; + if (fault) memset(fault,0,sizeof *fault); + if (!w || !code || !consumed || !event) return ES_EINVAL; + pc=w->cpu.pc; + if (available<2) return reject(fault,ESK_XT_TRUNCATED,pc,pc,0); + word=(uint32_t)code[0]|(uint32_t)code[1]<<8; width=(word&15u)<8 ? 3u : 2u; + if (available>4)&15u; s=(word>>8)&15u; + next=*w; save(&next); + if (op==5 && ((word>>4)&3u)) { /* CALL4/8/12 */ + n=(word>>4)&3u; kind=1; mask=bit(n*4); + target=(pc&UINT32_C(0xfffffffc))+4+((((word>>6)^UINT32_C(0x20000))-UINT32_C(0x20000))<<2); + } else if ((word&UINT32_C(0xfff00f))==0 && t>=13) { /* CALLX4/8/12 */ + n=t-12; kind=1; mask=bit(n*4)|bit(s); target=next.cpu.a[s]; + } else if ((word&255u)==0x36) { + kind=2; n=(next.ps>>16)&3u; + if (s>3 || !(next.ps&ESK_XT_PS_WOE) || (next.ps&ESK_XT_PS_EXCM)) + return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + mask=bit(n*4+s)|bit(s); + } else if (word==0x000090 || word==0xf01d) kind=3; + else if (word==0x003400 || word==0x003500) kind=4; + else if ((word&UINT32_C(0xffff0f))==0x408000) kind=5; + else if ((word&UINT32_C(0xff000f))==0x030000 || (word&UINT32_C(0xff000f))==0x130000) { + unsigned reg=(word>>8)&255u; + if (reg>2 && reg!=72 && reg!=73 && reg!=177 && reg!=230) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + kind=6; mask=bit(t); + } + else if (op==6 && t==7 && ((word>>12)&15u)>=8 && ((word>>12)&15u)<=10) { kind=8; mask=bit(s); } + else if ((word&UINT32_C(0xff000f))==0x090000 || (word&UINT32_C(0xff000f))==0x490000) { + if (!(next.ps&ESK_XT_PS_EXCM)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + kind=9; /* L32E/S32E in exception mode only */ + } + else if (word==0x002010) kind=7; /* RSYNC; no asynchronous pipeline here */ + else if (references(code,width,word,&mask)!=ES_OK) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + if (kind>=4 && kind<=6 && !(kind==6 && ((word>>8)&255u)<=2) && !(next.ps&ESK_XT_PS_EXCM) && ((next.ps>>6)&3u)) + return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); /* privilege dispatch not yet modelled */ + e=overflow(&next,mask); + if (e) { *w=next; *event=e; return ES_OK; } + if (kind==1) { + next.ps=(next.ps&~UINT32_C(0x30000)) | (n<<16); + next.cpu.a[n*4]=(n<<30)|((pc+3)&UINT32_C(0x3fffffff)); next.cpu.pc=target; flow=0; + } else if (kind==2) { + uint32_t sp=next.cpu.a[s]-((word>>12)<<3); + next.base=(next.base+n)&15u; next.start|=bit(next.base); + next.ar[(next.base*4+s)&63u]=sp; load(&next); next.cpu.pc=pc+3; + } else if (kind==3) { + unsigned back,nearest=0,old=next.base; + uint32_t link=next.cpu.a[0],return_pc=(pc&UINT32_C(0xc0000000))|(link&UINT32_C(0x3fffffff)); + n=link>>30; + for (back=1;back<=3;back++) if (next.start&bit(old-back)) { nearest=back; break; } + if (!n || (nearest && nearest!=n) || !(next.ps&ESK_XT_PS_WOE) || (next.ps&ESK_XT_PS_EXCM)) + return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + next.base=(old-n)&15u; + if (next.start&bit(next.base)) { next.start&=~bit(old); load(&next); next.cpu.pc=return_pc; } + else { + next.epc1=pc; next.ps=(next.ps&~UINT32_C(0xf00))|(old<<8)|ESK_XT_PS_EXCM; + load(&next); e=n*2; next.cpu.pc=next.vectors[e-1]; *w=next; *event=e; return ES_OK; + } + flow=0; + /* CALLINC is preserved; the ISA permits implementation-dependent update. */ + } else if (kind==4) { + if (!(next.ps&ESK_XT_PS_EXCM)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + if (word==0x003400) next.start&=~bit(next.base); else next.start|=bit(next.base); + next.base=(next.ps>>8)&15u; next.ps&=~ESK_XT_PS_EXCM; load(&next); next.cpu.pc=next.epc1; flow=0; + } else if (kind==5) { + next.base=(next.base+t)&15u; /* modulo 16 is identical for signed imm4 */ + load(&next); next.cpu.pc=pc+3; + } else if (kind==6) { + unsigned reg=(word>>8)&255u; uint32_t value=next.cpu.a[t]; + int write=(word&UINT32_C(0x100000))!=0; + if (reg>2 && reg!=72 && reg!=73 && reg!=177 && reg!=230) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + if (write) { + if (reg==0) next.lbeg=value; + else if (reg==1) next.lend=value; + else if (reg==2) next.lcount=value; + else if (reg==72) { next.base=value&15u; load(&next); } + else if (reg==73) next.start=value&65535u; + else if (reg==177) next.epc1=value; + else next.ps=value; + } else next.cpu.a[t]=reg==0 ? next.lbeg : reg==1 ? next.lend : reg==2 ? next.lcount : + reg==72 ? next.base : reg==73 ? next.start : reg==177 ? next.epc1 : next.ps; + next.cpu.pc=pc+3; + } else if (kind==8) { + unsigned condition=(word>>12)&15u; uint32_t count=next.cpu.a[s]; + next.lcount=count-1u; next.lbeg=pc+3; next.lend=pc+4+(word>>16); + next.cpu.pc=pc+3; + if ((condition==9 && !count) || (condition==10 && (!count || (count>>31)))) { + next.cpu.pc=next.lend; flow=0; + } + } else if (kind==9) { + uint32_t addr=next.cpu.a[s]+((((word>>12)&15u)-16u)<<2),value=0; + int write=(word&UINT32_C(0x400000))!=0; + if (addr&3u) return reject(fault,ESK_XT_ALIGN,pc,addr,word); + if (write) { + if (!memory || !memory->write || memory->write(memory->ctx,addr,4,next.cpu.a[t])!=ES_OK) + return reject(fault,ESK_XT_STORE,pc,addr,word); + } else { + if (!memory || !memory->read || memory->read(memory->ctx,addr,4,&value)!=ES_OK) + return reject(fault,ESK_XT_LOAD,pc,addr,word); + next.cpu.a[t]=value; + } + next.cpu.pc=pc+3; + } else if (kind==7) next.cpu.pc=pc+3; + else { + st=esk_xtensa_execute_flow(&next.cpu,memory,code,available,consumed,fault,&flow); + if (st!=ES_OK) return st; + } + if (flow && !(next.ps&ESK_XT_PS_EXCM) && next.lcount && next.cpu.pc==next.lend) { + next.lcount--; next.cpu.pc=next.lbeg; + } + save(&next); *w=next; *consumed=width; return ES_OK; +} +es_status esk_xtensa_window_step(esk_xtensa_windows *w, const esk_xtensa_memory *memory, + unsigned *event, esk_xtensa_fault *fault) +{ + unsigned char code[3]; unsigned i,width=2; size_t used; uint32_t value; + if (event) *event=0; + if (fault) memset(fault,0,sizeof *fault); + if (!w || !event) return ES_EINVAL; + for (i=0;icpu.pc+i; + if (!memory || !memory->read || memory->read(memory->ctx,address,1,&value)!=ES_OK) + return reject(fault,ESK_XT_FETCH,w->cpu.pc,address,0); + code[i]=(unsigned char)(value&255u); + if (i==0 && (code[0]&15u)<8) width=3; + } + return esk_xtensa_window_execute(w,memory,code,width,&used,event,fault); +} diff --git a/core/mcu/xtensa/windows.h b/core/mcu/xtensa/windows.h new file mode 100644 index 000000000..a9a8f7cb4 --- /dev/null +++ b/core/mcu/xtensa/windows.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_XTENSA_WINDOWS_H +#define ESK_XTENSA_WINDOWS_H +#include "mcu/xtensa/execute.h" +#define ESK_XT_PS_EXCM UINT32_C(0x10) +#define ESK_XT_PS_WOE UINT32_C(0x40000) +/* Experimental LX6 64-AR register-window execution, NOT SoC support. + * cpu.a is the editable visible cache; physical ARs synchronize on successful + * instructions/exception transitions. Init configures an ABI test state, not + * silicon power-on defaults. Vectors are caller-supplied, not inferred. + * Only PS/WINDOWBASE/WINDOWSTART/EPC1 and loop special registers are implemented. + * Generic exceptions, interrupts, MMU/caches and timing remain unsupported. */ +typedef struct esk_xtensa_windows { + esk_xtensa_state cpu; + uint32_t ar[64], ps, base, start, epc1, lbeg, lend, lcount; + uint32_t vectors[6]; /* overflow4, underflow4, overflow8, underflow8, overflow12, underflow12 */ +} esk_xtensa_windows; +/* event 0: an instruction retired; 1..6: window exception transition. On a + * window event consumed is zero (retry instruction after spill/fill handler). + * Window exceptions intentionally change state, including base, PS and PC. + * Other failures retain state and return the ordinary fault report. */ +void esk_xtensa_windows_init(esk_xtensa_windows *state, uint32_t pc, const uint32_t vectors[6]); +es_status esk_xtensa_window_execute(esk_xtensa_windows *state, const esk_xtensa_memory *memory, + const unsigned char *code, size_t available, + size_t *consumed, unsigned *event, esk_xtensa_fault *fault); +es_status esk_xtensa_window_step(esk_xtensa_windows *state, const esk_xtensa_memory *memory, + unsigned *event, esk_xtensa_fault *fault); +#endif diff --git a/docs/STATUS.md b/docs/STATUS.md index a3b779387..84f172fd2 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -21,9 +21,16 @@ window: 111 native assertions and 5,376 separately assembled all-register compar pass. Mixed-width fetch, compact arithmetic, non-windowed calls/returns, conditional branches and callback memory access now execute an assembled summing/subroutine program. The execution test passes 618 assertions; a separate assembler/linker -referee passes 3,840 execution cases and 960 failed accesses. Architectural -exceptions, windows, interrupts, full ISA, SMP, SoC boot and timing remain -unimplemented. Additionally, 96 compiler-generated C executions (sum/sort/copy, +referee passes 3,840 execution cases and 960 failed accesses. General +exceptions/interrupts, full ISA, SMP, SoC boot and timing remain +unimplemented. The new 64-AR window engine implements windowed calls/ENTRY/RETW, +lazy spill/fill exception transitions, RFWO/RFWU retries, selected special +registers and hardware loops (1,337 native assertions). The actual Espressif +ESP32 GCC toolchain passes 128 shallow windowed C runs. A further suite executes +ESP-IDF's actual spill/fill handler instruction bodies through the bus, passing +224 windowed-ABI runs (160 C, 64 assembly) and all six event paths with matching +overflow/underflow counts. This does not establish ESP-IDF boot or SoC fidelity. +Additionally, 96 compiler-generated C executions (sum/sort/copy, 33,472 fetched instructions) pass buffer, guard, return and callee-save checks. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 93ce853c6..3dec03a0f 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -1,6 +1,6 @@ # Original ESP32 dual-core target: identity and implementation gates -**Status: firmware inspection and experimental integer kernel implemented; no executable target is added.** Requested by +**Status: firmware inspection and experimental integer/window/loop execution implemented; no executable target is added.** Requested by the owner on 2026-11-04. This is the original ESP32 family, not ESP32-C3, ESP32-S2 or ESP32-S3. The generic marketplace title “ESP-32 / ESP-32S WiFi+Bluetooth Dual Cores” does not identify a reproducible board revision. @@ -126,9 +126,101 @@ python tests/vendor/xtensa_integer/run_c.py \ ``` No CPU constructor, GDB register mapping or board catalog support is registered. -Full instruction coverage, windowed ABI, system registers, exception/interrupt -handling, synchronization, dual-core execution, SoC boot and peripherals still -need implementation. Single-step synthetic programs do not qualify ESP-IDF boot. +Full instruction coverage, general exception/interrupt handling, complete system +registers, synchronization, dual-core execution, SoC boot and peripherals still +need implementation. Selected windowed-ABI support is described below. Single-step synthetic programs do not qualify ESP-IDF boot. + +## Implemented: register windows, spill/fill transitions and hardware loops + +`core/mcu/xtensa/windows.c` wraps execution with a 64-physical-AR register file, +16 four-register window positions and exact read/write operand references for +the implemented instruction set. The visible register cache can be edited by +the caller; it synchronizes with the physical file on successful transitions. +Initialization configures an ABI test state, not ESP32 power-on reset defaults. + +Implemented window behavior: + +- CALL4/8/12 and CALLX4/8/12 encode the return link and CALLINC without rotating. + ENTRY allocates its frame from the old stack pointer and then rotates, with + physical register overlap, wraparound and WindowStart tracking. +- RETW/RETW.N reconstruct the return PC's high bits, validate the return window + distance and clear the departing active-window bit on successful return. +- Lazy overflow checks consider actual operands, not all 16 visible registers. + They select the overflow4/8/12 event from the occupied window layout. +- Underflow4/8/12 returns move to the caller window and record exception state. + Both overflow and underflow set EXCM, OWB and EPC1 and transfer to caller- + supplied vector addresses. They consume zero instruction bytes: the faulting + instruction is retried after RFWO/RFWU restores the original window/PC. +- RFWO clears the spilled window bit; RFWU sets the filled bit. Physical register + state is retained across the handler and retry. L32E/S32E support exception-mode + spill/fill memory accesses, with natural alignment and failed-access retention. +- ROTW and RSR/WSR for PS, WINDOWBASE, WINDOWSTART and EPC1 are supported. + Privileged operations reject unsupported user-mode execution. General privilege + exception dispatch is not implemented. PS fields unrelated to these mechanisms + are retained but do not implement interrupts, security or other system behavior. + +The module preserves CALLINC on RETW, one of the ISA-permitted implementation +choices; exact silicon-specific behavior still needs independent qualification. +L32E/S32E outside exception mode is intentionally unsupported. RSYNC has no +asynchronous pipeline to drain in this sequential functional engine. No cache, +MMU, memory-protection, pipeline timing or SoC vector map is inferred. + +LOOP, LOOPNEZ and LOOPGTZ update LBEG/LEND/LCOUNT; their RSR/WSR access is +supported. Sequential loop-end execution loops back and decrements LCOUNT, +while taken branches, jumps, calls and returns do not. This distinction is +explicit even when a branch target equals its fallthrough address. EXCM disables +loop-back during handlers. Unconditional LOOP with count zero retains the +architectural 2^32 iteration count; conditional zero/negative skips are distinct. +Pipeline synchronization, illegal/restricted endpoint sequences and processor- +specific loop alignment constraints are not qualified by this functional model. + +### Windowed-ABI evidence + +The native window test has 1,337 assertions: CALL4/8/12 across every physical +window position, stack/link/argument/result overlap, lazy overflow and retries, +underflow restore/retry, physical wraparound, special registers, invalid modes, +loop limits/exception behavior, taken-versus-sequential end handling and failed +exception-memory accesses. The existing integer/execution tests still pass. + +The actual ESP32 toolchain is Espressif's GCC 8.4.0 esp-2021r2-patch5: + +- [Archive](https://dl.espressif.com/dl/xtensa-esp32-elf-gcc8_4_0-esp-2021r2-patch5-linux-amd64.tar.gz) +- SHA-256 `8ef14e0409c2011b41e504a30f70d3e35287313a795d1f2462ad2cd0e2052d37`, + verified against [ESP-IDF v4.4.7 tools.json](https://github.com/espressif/esp-idf/blob/v4.4.7/tools/tools.json). + +The optional `run_windows_c.py` first passes 128 real ESP32 windowed-ABI C runs: +sum, sort, copy and nested calls, including compiler-emitted hardware loops +(35,165 retired instructions). With the vendor vector source below it passes +224 runs: 160 compiled C runs and 64 project assembly recursion runs, retiring +63,337 instructions. Deep recursion exercises every overflow/underflow type: +204/204 events for 4-register frames, 300/300 for 8-register frames and 351/351 +for 12-register frames. Results, memory guards, caller windows and returns pass. + +The six spill/fill handler instruction bodies are extracted without instruction +changes from [ESP-IDF v4.4.7 xtensa_vectors.S](https://github.com/espressif/esp-idf/blob/v4.4.7/components/freertos/port/xtensa/xtensa_vectors.S). +Expected source SHA-256: +`29b544a01b708ca59af85ec6caa80101fba33345d8ec765e6a8fd832b1728366`. +The runner rejects source drift, retains the source's Cadence copyright and +permissive permission notice in the generated assembly, and compiles only the +six handlers at their original relative offsets. Toolchain and vendor source +are external test inputs, not linked into EmbSIM or bundled in the catalog. +This is the source's permissive software grant, not permission to redistribute +unrelated vendor documents or ROMs. + +```sh +python tests/vendor/xtensa_integer/run_windows_c.py \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld \ + --nm /path/to/xtensa-esp32-elf-nm \ + --vectors-source /path/to/xtensa_vectors.S +``` + +Test vectors and memory addresses are configured by the runner; they are not +an ESP32 memory map. The harness initializes a valid caller window/stack. The +vendor handlers perform actual bus loads/stores and RFWO/RFWU retries; no host +shadow-register restore is used. General RTOS exception/interrupt code, MOVSP, +full context switching, ESP-IDF boot and SoC devices remain outside this evidence. ## Implemented: standalone firmware integrity inspection diff --git a/tests/unit/test_xtensa_windows.c b/tests/unit/test_xtensa_windows.c new file mode 100644 index 000000000..27bd4fc9d --- /dev/null +++ b/tests/unit/test_xtensa_windows.c @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/xtensa/windows.h" + +static const uint32_t vectors[6]={0x40010000,0x40010040,0x40010080,0x400100c0,0x40010100,0x40010140}; +static es_status execute_bus(esk_xtensa_windows *w,const esk_xtensa_memory *memory,uint32_t word,unsigned bytes,unsigned *event) +{ + unsigned char code[3]; size_t used=99; esk_xtensa_fault fault; es_status st; + code[0]=(unsigned char)(word&255u); code[1]=(unsigned char)((word>>8)&255u); + code[2]=(unsigned char)((word>>16)&255u); + st=esk_xtensa_window_execute(w,memory,code,bytes,&used,event,&fault); + CHECK_EQ(used,st==ES_OK && !*event ? bytes : 0u); + return st; +} +static es_status execute(esk_xtensa_windows *w,uint32_t word,unsigned bytes,unsigned *event) +{ + return execute_bus(w,NULL,word,bytes,event); +} +static void call_entry_return(void) +{ + unsigned n,base; + for (base=0;base<16;base++) for (n=1;n<=3;n++) { + esk_xtensa_windows w; unsigned event; uint32_t caller_sp=0x3ffb0100; + esk_xtensa_windows_init(&w,0x40000000,vectors); + w.base=base; w.start=1u<>16)&3u,n); + CHECK_EQ(execute(&w,0x004136,3,&event),ES_OK); /* entry a1,32 */ + CHECK_EQ(w.base,(base+n)&15u); CHECK_EQ(w.cpu.a[1],caller_sp-32u); + CHECK_EQ(w.cpu.a[0],(n<<30)|3u); CHECK_EQ(w.cpu.a[2],7); + CHECK_EQ(execute(&w,0x01c222,3,&event),ES_OK); CHECK_EQ(w.cpu.a[2],8); + CHECK_EQ(execute(&w,0xf01d,2,&event),ES_OK); CHECK_EQ(event,0); + CHECK_EQ(w.cpu.pc,0x40000003u); CHECK_EQ(w.base,base); + CHECK_EQ(w.start,1u<>8)&15u,3); CHECK_EQ(w.start,8); + w.cpu.a[2]=0xdeadbeef; /* handler restores one caller register */ + CHECK_EQ(execute(&w,0x003500,3,&event),ES_OK); CHECK_EQ(event,0); + CHECK_EQ(w.base,3); CHECK_EQ(w.start,10); CHECK_EQ(w.cpu.pc,0x40001234u); + CHECK_EQ(w.ps&ESK_XT_PS_EXCM,0); + CHECK_EQ(execute(&w,0xf01d,2,&event),ES_OK); CHECK_EQ(event,0); + CHECK_EQ(w.base,1); CHECK_EQ(w.start,2); CHECK_EQ(w.cpu.pc,0x40005678u); + CHECK_EQ(w.cpu.a[2],0xdeadbeefu); +} +static void special_and_invalid(void) +{ + esk_xtensa_windows w,before; unsigned event; + esk_xtensa_windows_init(&w,0x40000000,vectors); + w.cpu.a[3]=2; CHECK_EQ(execute(&w,0x134830,3,&event),ES_OK); CHECK_EQ(w.base,2); + CHECK_EQ(execute(&w,0x034820,3,&event),ES_OK); CHECK_EQ(w.cpu.a[2],2); + CHECK_EQ(execute(&w,0x4080d0,3,&event),ES_OK); CHECK_EQ(w.base,15); /* rotw -3 */ + w.cpu.a[3]=0xabcd; CHECK_EQ(execute(&w,0x134930,3,&event),ES_OK); CHECK_EQ(w.start,0xabcd); + w.ps=ESK_XT_PS_WOE|64u; before=w; + CHECK_EQ(execute(&w,0x408000,3,&event),ES_EUNSUPPORTED); CHECK_EQ(memcmp(&w,&before,sizeof w),0); + w.ps=0; before=w; CHECK_EQ(execute(&w,0x004136,3,&event),ES_EUNSUPPORTED); + CHECK_EQ(memcmp(&w,&before,sizeof w),0); + w.ps=ESK_XT_PS_WOE; w.cpu.a[0]=0; before=w; + CHECK_EQ(execute(&w,0xf01d,2,&event),ES_EUNSUPPORTED); CHECK_EQ(memcmp(&w,&before,sizeof w),0); + before=w; CHECK_EQ(execute(&w,0xdead00,3,&event),ES_EUNSUPPORTED); + CHECK_EQ(memcmp(&w,&before,sizeof w),0); +} +static void hardware_loops(void) +{ + esk_xtensa_windows w; unsigned event; + esk_xtensa_windows_init(&w,0x40000001,vectors); w.cpu.a[2]=3; + CHECK_EQ(execute(&w,0x018276,3,&event),ES_OK); /* loop a2, PC+5 */ + CHECK_EQ(w.lbeg,0x40000004u); CHECK_EQ(w.lend,0x40000006u); CHECK_EQ(w.lcount,2); + CHECK_EQ(execute(&w,0xf03d,2,&event),ES_OK); CHECK_EQ(w.cpu.pc,w.lbeg); CHECK_EQ(w.lcount,1); + CHECK_EQ(execute(&w,0xf03d,2,&event),ES_OK); CHECK_EQ(w.cpu.pc,w.lbeg); CHECK_EQ(w.lcount,0); + CHECK_EQ(execute(&w,0xf03d,2,&event),ES_OK); CHECK_EQ(w.cpu.pc,w.lend); + w.cpu.pc=0x40000001; w.cpu.a[3]=0; + CHECK_EQ(execute(&w,0x019376,3,&event),ES_OK); /* loopnez a3 */ + CHECK_EQ(w.cpu.pc,0x40000006u); CHECK_EQ(w.lcount,UINT32_MAX); + w.cpu.pc=0x40000001; w.cpu.a[4]=0x80000000u; + CHECK_EQ(execute(&w,0x01a476,3,&event),ES_OK); /* loopgtz a4 */ + CHECK_EQ(w.cpu.pc,0x40000006u); + w.cpu.pc=0x40000001; w.cpu.a[2]=0; + CHECK_EQ(execute(&w,0x018276,3,&event),ES_OK); CHECK_EQ(w.cpu.pc,w.lbeg); CHECK_EQ(w.lcount,UINT32_MAX); + /* Exception handlers are not controlled by an interrupted loop. */ + w.cpu.pc=w.lend-2; w.lcount=2; w.ps|=ESK_XT_PS_EXCM; + CHECK_EQ(execute(&w,0xf03d,2,&event),ES_OK); CHECK_EQ(w.cpu.pc,w.lend); CHECK_EQ(w.lcount,2); + /* Taken branch targets fallthrough PC exactly: it must still exit loop. */ + w.ps&=~ESK_XT_PS_EXCM; w.lbeg=0x40000004; w.lend=0x40000010; w.lcount=5; + w.cpu.pc=w.lend-3; w.cpu.a[2]=1; + CHECK_EQ(execute(&w,0xfff256,3,&event),ES_OK); CHECK_EQ(w.cpu.pc,w.lend); CHECK_EQ(w.lcount,5); + w.cpu.pc=w.lend-3; w.cpu.a[2]=0; + CHECK_EQ(execute(&w,0xfff256,3,&event),ES_OK); CHECK_EQ(w.cpu.pc,w.lbeg); CHECK_EQ(w.lcount,4); +} +typedef struct test_memory { uint32_t value; unsigned reads,writes,fail; } test_memory; +static es_status mem_read(void *ctx,uint32_t address,unsigned bytes,uint32_t *value) +{ + test_memory *m=(test_memory *)ctx; m->reads++; + if (m->fail || address!=128 || bytes!=4) return ES_EINVAL; + *value=m->value; return ES_OK; +} +static es_status mem_write(void *ctx,uint32_t address,unsigned bytes,uint32_t value) +{ + test_memory *m=(test_memory *)ctx; m->writes++; + if (m->fail || address!=128 || bytes!=4) return ES_EINVAL; + m->value=value; return ES_OK; +} +static void exception_memory(void) +{ + esk_xtensa_windows w,before; unsigned event; + test_memory m={0x89abcdef,0,0,0}; esk_xtensa_memory bus={&m,mem_read,mem_write}; + esk_xtensa_windows_init(&w,0x40000000,vectors); w.cpu.a[3]=192; before=w; + CHECK_EQ(execute_bus(&w,&bus,0x090320,3,&event),ES_EUNSUPPORTED); /* L32E only in EXCM */ + CHECK_EQ(m.reads,0); CHECK_EQ(memcmp(&w,&before,sizeof w),0); + w.ps|=ESK_XT_PS_EXCM; + CHECK_EQ(execute_bus(&w,&bus,0x090320,3,&event),ES_OK); CHECK_EQ(w.cpu.a[2],0x89abcdefu); + w.cpu.a[6]=0x12345678; w.cpu.a[7]=132; + CHECK_EQ(execute_bus(&w,&bus,0x49f760,3,&event),ES_OK); CHECK_EQ(m.value,0x12345678u); + before=w; m.fail=1; + CHECK_EQ(execute_bus(&w,&bus,0x49f760,3,&event),ES_EINVAL); + CHECK_EQ(memcmp(&w,&before,sizeof w),0); CHECK_EQ(m.value,0x12345678u); + CHECK_EQ(execute_bus(&w,&bus,0x090320,3,&event),ES_EINVAL); + CHECK_EQ(memcmp(&w,&before,sizeof w),0); + w.cpu.a[7]=133; before=w; { unsigned writes=m.writes; + CHECK_EQ(execute_bus(&w,&bus,0x49f760,3,&event),ES_EINVAL); + CHECK_EQ(m.writes,writes); CHECK_EQ(memcmp(&w,&before,sizeof w),0); + } +} +TEST_MAIN_BEGIN + (void)t_str_eq; + RUN(call_entry_return); RUN(overflow_retry); RUN(underflow_retry); RUN(special_and_invalid); RUN(hardware_loops); RUN(exception_memory); +TEST_MAIN_END diff --git a/tests/vendor/README.md b/tests/vendor/README.md index ff35fa71e..942479ecb 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -42,3 +42,16 @@ bus. Supply `--compiler`, `--objcopy`, `--linker` and `--nm`. Checks include who buffers and guards, source retention, return PC and callee-saved registers. The reference result used GCC xtensa-lx106 10.2.1 at `-O1`; this tests shared instructions, not the full LX6 configuration, ESP-IDF boot or a real board. + +`xtensa_integer/run_windows_c.py` uses the actual Espressif ESP32 windowed-ABI +compiler. Without vector input, 128 sum/sort/copy/nested-call executions pass. +With `--vectors-source` pointing to the hash-pinned ESP-IDF v4.4.7 +`components/freertos/port/xtensa/xtensa_vectors.S`, 224 runs (160 C, 64 assembly) +execute all six vendor window spill/fill bodies via actual bus accesses and +RFWO/RFWU retries. The generated temporary assembly retains the source's +permissive copyright/permission notice; no vendor handler is linked into the +app. The runner validates the source SHA-256 before extraction. See the target +contract for exact toolchain/source hashes and commands. This qualifies selected +window/loop behavior, not general RTOS context switching, ESP-IDF boot or SoC +peripherals. Compiler/linker/objcopy/nm are external tools, not dependencies of +the runnable app. diff --git a/tests/vendor/xtensa_integer/run_windows_c.py b/tests/vendor/xtensa_integer/run_windows_c.py new file mode 100644 index 000000000..4a4a769e4 --- /dev/null +++ b/tests/vendor/xtensa_integer/run_windows_c.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""Run real ESP32 windowed-ABI compiler output; no ESP-IDF/SoC boot claim.""" +import argparse +import ctypes as C +from pathlib import Path +import random +import hashlib +import re +import subprocess +import tempfile +from run_execute import State, Fault, Read, Write, Memory, ROOT, MASK +from run_c import SOURCE + + +class Windows(C.Structure): + _fields_=[('cpu',State),('ar',C.c_uint32*64),('ps',C.c_uint32),('base',C.c_uint32), + ('start',C.c_uint32),('epc1',C.c_uint32),('lbeg',C.c_uint32),('lend',C.c_uint32), + ('lcount',C.c_uint32),('vectors',C.c_uint32*6)] + + +NESTED=''' +unsigned leaf(unsigned x) { return x+3; } +unsigned middle(unsigned x) { return leaf(x)*2; } +unsigned nested(unsigned x) { return middle(x)+leaf(x+1); } +''' + + +def main(): + ap=argparse.ArgumentParser(description=__doc__) + for name in ['compiler','objcopy','linker','nm']:ap.add_argument('--'+name,required=True) + ap.add_argument('--vectors-source',type=Path) + ap.add_argument('--assembler') + ap.add_argument('--cc',default='cc');args=ap.parse_args();rng=random.Random(0x64a) + with tempfile.TemporaryDirectory(prefix='embsim-window-c-') as tmp: + out=Path(tmp);src=out/'routines.c';obj=out/'routines.o';elf=out/'routines.elf';binary=out/'routines.bin' + extra_objects=[] + if args.vectors_source: + vendor=args.vectors_source.read_bytes() + assert hashlib.sha256(vendor).hexdigest()=='29b544a01b708ca59af85ec6caa80101fba33345d8ec765e6a8fd832b1728366', 'Unexpected vendor vector source' + text=vendor.decode('utf-8') + # Keep the vendor's complete permissive copyright/permission notice. + notice=text[:text.index('--------------------------------------------------------------------------------')]+'*/\n' + assembly=notice+'.section .vectors,"ax"\n.begin no-transform\n' + for i,name in enumerate(['_WindowOverflow4','_WindowUnderflow4','_WindowOverflow8','_WindowUnderflow8','_WindowOverflow12','_WindowUnderflow12']): + match=re.search(r'^'+name+r':\n.*?^\s+rfw[ou][^\n]*',text,re.M|re.S) + assert match,name + assembly+=f'.org {i*64}\n.global {name}\n'+match.group(0)+'\n' + assembly+='.end no-transform\n.text\n.begin no-transform\n' + for increment in [4,12]: + arg=increment+2;name=f'recurse{increment}' + assembly+=f'.align 4\n.global {name}\n{name}:\nentry a1,64\nbeqz a2,.Lbase{increment}\naddi a{arg},a2,-1\ncall{increment} {name}\nadd a2,a{arg},a2\nretw.n\n.Lbase{increment}:\nmovi.n a2,0\nretw.n\n' + assembly+='.end no-transform\n';asm=out/'vendor-vectors.S';asm.write_text(assembly,encoding='utf-8') + assembler=args.assembler or str(Path(args.compiler).with_name(Path(args.compiler).name.replace('gcc','as'))) + asmobj=out/'vendor-vectors.o';subprocess.run([assembler,str(asm),'-o',str(asmobj)],check=True);extra_objects=[str(asmobj)] + recursive='unsigned recurse(unsigned n,unsigned seed) { if (!n) return seed; return recurse(n-1,seed+3)+n; }' if args.vectors_source else '' + src.write_text(SOURCE+NESTED+recursive,encoding='utf-8') + subprocess.run([args.compiler,'-B'+str(Path(args.compiler).parent)+'/', '-O1','-ffreestanding','-fno-builtin','-fno-inline','-c',str(src),'-o',str(obj)],check=True) + subprocess.run([args.linker,'--no-relax','-Ttext=0x1000','--entry=sum','--section-start=.vectors=0x8000',str(obj),*extra_objects,'-o',str(elf)],check=True) + subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(elf),str(binary)],check=True) + symbols={} + for line in subprocess.check_output([args.nm,str(elf)],text=True).splitlines(): + fields=line.split() + if len(fields)==3:symbols[fields[2]]=int(fields[0],16) + lib=out/'windows.so' + subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC', + '-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'), + *[str(ROOT/'core/mcu/xtensa'/file) for file in ['integer.c','execute.c','windows.c']],'-o',str(lib)],check=True) + api=C.CDLL(str(lib));init=api.esk_xtensa_windows_init + init.argtypes=[C.POINTER(Windows),C.c_uint32,C.POINTER(C.c_uint32)];init.restype=None + step=api.esk_xtensa_window_step;step.argtypes=[C.POINTER(Windows),C.POINTER(Memory),C.POINTER(C.c_uint),C.POINTER(Fault)];step.restype=C.c_int + code=binary.read_bytes();checks=0;instructions=0;events=[0]*6;vcode=b'' + if args.vectors_source: + vectorbin=out/'vectors.bin';subprocess.run([args.objcopy,'-O','binary','--only-section=.vectors',str(elf),str(vectorbin)],check=True);vcode=vectorbin.read_bytes() + for name in ['sum','insertion_sort','copy_bytes','nested']+(['recurse','recurse4','recurse12'] if args.vectors_source else []): + for length in range(32): + values=[rng.getrandbits(32) for _ in range(length)] + if length%4==0:values=sorted(values,reverse=True) + if length%4==1:values=[MASK]*length + data=bytearray([0xa5]*8192);base=0x200000 + original=b''.join(v.to_bytes(4,'little') for v in values);data[16:16+len(original)]=original + initial=bytes(data) + @Read + def read(ctx,address,size,value): + if 0x1000<=address and address+size<=0x1000+len(code):b=code[address-0x1000:address-0x1000+size] + elif 0x8000<=address and address+size<=0x8000+len(vcode):b=vcode[address-0x8000:address-0x8000+size] + elif base<=address and address+size<=base+len(data):b=data[address-base:address-base+size] + else:return -1 + value[0]=int.from_bytes(b,'little');return 0 + @Write + def write(ctx,address,size,value): + if not (base<=address and address+size<=base+len(data)):return -1 + data[address-base:address-base+size]=(value&((1<<(8*size))-1)).to_bytes(size,'little');return 0 + state=Windows();vectors=(C.c_uint32*6)(*[(0x8000 if args.vectors_source else 0x40000000)+i*64 for i in range(6)]) + init(C.byref(state),symbols[name],vectors);state.ps|=1<<16 + stop=0x3ffffff0;state.cpu.a[4]=(1<<30)|stop;state.cpu.a[0]=0x12345678;state.cpu.a[1]=base+8000 + state.cpu.a[6]=base+16;state.cpu.a[7]=length + argument=rng.getrandbits(32) + if name=='nested':state.cpu.a[6]=argument + if name.startswith('recurse'):state.cpu.a[6]=length;state.cpu.a[7]=argument + if name=='copy_bytes':state.cpu.a[6]=base+2048;state.cpu.a[7]=base+16;state.cpu.a[8]=length*4 + bus=Memory(None,read,write);fault=Fault();event=C.c_uint();count=0;retired=0 + while state.cpu.pc!=stop and count<30000: + status=step(C.byref(state),C.byref(bus),C.byref(event),C.byref(fault)) + assert status==0 and (args.vectors_source or event.value==0),(name,length,count,hex(fault.pc),hex(fault.opcode),fault.kind,event.value,hex(state.cpu.pc)) + count+=1 + if event.value:events[event.value-1]+=1 + else:retired+=1 + assert state.cpu.pc==stop and state.base==0 and state.start==1,(name,length,'return/window state') + assert state.cpu.a[0]==0x12345678 and state.cpu.a[1]==base+8000,(name,length,'caller state') + if name=='sum':assert state.cpu.a[6]==sum(values)&MASK and bytes(data)==initial + elif name=='nested': + assert state.cpu.a[6]==(3*argument+10)&MASK,(argument,state.cpu.a[6]) + assert data[:4000]==initial[:4000], 'nested output/guard corruption' + elif name.startswith('recurse'): + expected_result=(argument+length*3+length*(length+1)//2) if name=='recurse' else length*(length+1)//2 + assert state.cpu.a[6]==expected_result&MASK,(name,length,state.cpu.a[6],expected_result&MASK) + assert data[:4000]==initial[:4000] and data[8016:]==initial[8016:],'stack guards' + elif name=='insertion_sort': + expected=bytearray(initial);expected[16:16+len(original)]=b''.join(v.to_bytes(4,'little') for v in sorted(values)) + assert data==expected,(name,length) + else: + expected=bytearray(initial);expected[2048:2048+len(original)]=original + assert data==expected,(name,length) + checks+=1;instructions+=retired + if args.vectors_source: + assert all(events) and events[::2]==events[1::2],events + print('Window events (overflow4/underflow4/overflow8/underflow8/overflow12/underflow12):',events) + print(f'{checks} ESP32 windowed-ABI executions pass ({160 if args.vectors_source else 128} C, {64 if args.vectors_source else 0} assembly); {instructions} instructions; windows/loops/data/returns verified') + + +if __name__=='__main__':main() From b07a2c438c08911c732021052fc07a594e387dbb Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Wed, 4 Nov 2026 22:04:27 +0000 Subject: [PATCH 08/57] Execute Xtensa SAR operations and selected XEA2 system exceptions --- CHANGELOG.md | 13 ++ core/mcu/xtensa/windows.c | 159 ++++++++++++++----- core/mcu/xtensa/windows.h | 20 ++- docs/STATUS.md | 9 +- docs/engineering/esp32-dual-core.md | 69 +++++++- tests/unit/test_xtensa_system.c | 133 ++++++++++++++++ tests/vendor/README.md | 6 + tests/vendor/xtensa_integer/run_system_c.py | 85 ++++++++++ tests/vendor/xtensa_integer/run_windows_c.py | 3 +- 9 files changed, 450 insertions(+), 47 deletions(-) create mode 100644 tests/unit/test_xtensa_system.c create mode 100644 tests/vendor/xtensa_integer/run_system_c.py diff --git a/CHANGELOG.md b/CHANGELOG.md index fe6fee8b4..c47806338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental Xtensa SAR and selected system behavior + +- Added SAR setup and variable shifts/funnel operations, XSR.SAR, selected system + registers, RSIL mask-state changes and serialized MEMW/ISYNC/ESYNC behavior. + This does not qualify interrupt delivery, cache/pipeline or async bus behavior. +- Added explicit opt-in kernel/user/double exception vectors for syscall, known + illegal instructions and implemented privilege faults, with EPC1/DEPC retention, + RFE/RFDE and explicit cause/address injection. Unimplemented valid opcodes and + ambiguous bus failures remain diagnostics rather than fabricated causes. +- Added 785 native assertions and 7,040 actual ESP32 compiled bit-manipulation + runs, alongside regression checks of window spill/fill. Full system/interrupt, + SMP, SoC boot and board support remain incomplete. + ### Experimental Xtensa windows and hardware loops - Implemented 64 physical address registers, overlapping windows, CALL4/8/12 diff --git a/core/mcu/xtensa/windows.c b/core/mcu/xtensa/windows.c index 19b4f15f4..1eb5cfdc0 100644 --- a/core/mcu/xtensa/windows.c +++ b/core/mcu/xtensa/windows.c @@ -4,6 +4,13 @@ #include "mcu/xtensa/integer.h" #include +enum execution_kind { + XT_BASE, XT_CALL, XT_ENTRY, XT_RETW, XT_WINDOW_RETURN, XT_ROTW, + XT_SPECIAL, XT_RSYNC, XT_LOOP, XT_EXCEPTION_MEMORY, + XT_SAR_SETUP=11, XT_SAR_SHIFT, XT_FENCE, XT_RSIL, XT_SYSCALL, + XT_EXCEPTION_RETURN, XT_ILLEGAL +}; + static uint32_t bit(unsigned n) { return UINT32_C(1) << (n & 15u); } static void save(esk_xtensa_windows *w) { @@ -24,6 +31,50 @@ void esk_xtensa_windows_init(esk_xtensa_windows *w, uint32_t pc, const uint32_t memset(w,0,sizeof *w); w->cpu.pc=pc; w->ps=ESK_XT_PS_WOE; w->start=1; if (vectors) memcpy(w->vectors,vectors,sizeof w->vectors); } +void esk_xtensa_exception_configure(esk_xtensa_windows *w, const uint32_t vectors[3]) +{ + if (!w) return; + w->exceptions_enabled=vectors ? 1u : 0u; + if (vectors) memcpy(w->exception_vectors,vectors,sizeof w->exception_vectors); +} +es_status esk_xtensa_raise_exception(esk_xtensa_windows *w, unsigned cause, + uint32_t address, int has_address, unsigned *event) +{ + unsigned index; + if (event) *event=0; + if (!w || !event || cause>63) return ES_EINVAL; + if (!w->exceptions_enabled) return ES_EUNSUPPORTED; + if (w->ps&ESK_XT_PS_EXCM) { w->depc=w->cpu.pc; index=2; } + else { w->epc1=w->cpu.pc; index=(w->ps&32u) ? 1u : 0u; } + w->exccause=cause; + if (has_address) w->excvaddr=address; + w->ps|=ESK_XT_PS_EXCM; w->cpu.pc=w->exception_vectors[index]; + *event=7u+index; + return ES_OK; +} +static int special_valid(unsigned reg) +{ + return reg<=3 || reg==72 || reg==73 || reg==177 || reg==192 || reg==230 || reg==232 || reg==238; +} +static uint32_t special_read(const esk_xtensa_windows *w,unsigned reg) +{ + switch (reg) { + case 0:return w->lbeg; case 1:return w->lend; case 2:return w->lcount; case 3:return w->sar; + case 72:return w->base; case 73:return w->start; case 177:return w->epc1; + case 192:return w->depc; case 230:return w->ps; case 232:return w->exccause; default:return w->excvaddr; + } +} +static void special_write(esk_xtensa_windows *w,unsigned reg,uint32_t value) +{ + switch (reg) { + case 0:w->lbeg=value;break; case 1:w->lend=value;break; case 2:w->lcount=value;break; + case 3:w->sar=value&63u;break; + case 72:w->base=value&15u;load(w);break; + case 73:w->start=value&65535u;break; + case 177:w->epc1=value;break; case 192:w->depc=value;break; + case 230:w->ps=value;break; case 232:w->exccause=value;break; default:w->excvaddr=value;break; + } +} /* Register references are checked before any access. Zero/compare branches do * not reference their instruction fields as destination register numbers. */ static es_status references(const unsigned char *code, unsigned width, uint32_t word, uint32_t *mask) @@ -65,7 +116,8 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo size_t *consumed, unsigned *event, esk_xtensa_fault *fault) { esk_xtensa_windows next; - uint32_t word,pc,mask=0,target=0; unsigned width,op,t,s,n=0,kind=0,e,flow=1; + uint32_t word,pc,mask=0,target=0; unsigned width,op,t,s,r,n=0,e,flow=1; + enum execution_kind kind=XT_BASE; es_status st; if (consumed) *consumed=0; if (event) *event=0; @@ -76,45 +128,62 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo word=(uint32_t)code[0]|(uint32_t)code[1]<<8; width=(word&15u)<8 ? 3u : 2u; if (available>4)&15u; s=(word>>8)&15u; + op=word&15u; t=(word>>4)&15u; s=(word>>8)&15u; r=(word>>12)&15u; next=*w; save(&next); if (op==5 && ((word>>4)&3u)) { /* CALL4/8/12 */ - n=(word>>4)&3u; kind=1; mask=bit(n*4); + n=(word>>4)&3u; kind=XT_CALL; mask=bit(n*4); target=(pc&UINT32_C(0xfffffffc))+4+((((word>>6)^UINT32_C(0x20000))-UINT32_C(0x20000))<<2); } else if ((word&UINT32_C(0xfff00f))==0 && t>=13) { /* CALLX4/8/12 */ - n=t-12; kind=1; mask=bit(n*4)|bit(s); target=next.cpu.a[s]; + n=t-12; kind=XT_CALL; mask=bit(n*4)|bit(s); target=next.cpu.a[s]; } else if ((word&255u)==0x36) { - kind=2; n=(next.ps>>16)&3u; + kind=XT_ENTRY; n=(next.ps>>16)&3u; if (s>3 || !(next.ps&ESK_XT_PS_WOE) || (next.ps&ESK_XT_PS_EXCM)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); mask=bit(n*4+s)|bit(s); - } else if (word==0x000090 || word==0xf01d) kind=3; - else if (word==0x003400 || word==0x003500) kind=4; - else if ((word&UINT32_C(0xffff0f))==0x408000) kind=5; - else if ((word&UINT32_C(0xff000f))==0x030000 || (word&UINT32_C(0xff000f))==0x130000) { + } else if (word==0x000090 || word==0xf01d) kind=XT_RETW; + else if (word==0x003400 || word==0x003500) kind=XT_WINDOW_RETURN; + else if ((word&UINT32_C(0xffff0f))==0x408000) kind=XT_ROTW; + else if ((word&UINT32_C(0xff000f))==0x030000 || (word&UINT32_C(0xff000f))==0x130000 || (word&UINT32_C(0xff000f))==0x610000) { unsigned reg=(word>>8)&255u; - if (reg>2 && reg!=72 && reg!=73 && reg!=177 && reg!=230) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); - kind=6; mask=bit(t); + if (!special_valid(reg) || ((word&UINT32_C(0xff000f))==0x610000 && reg!=3)) + return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + kind=XT_SPECIAL; mask=bit(t); } - else if (op==6 && t==7 && ((word>>12)&15u)>=8 && ((word>>12)&15u)<=10) { kind=8; mask=bit(s); } + else if (op==6 && t==7 && ((word>>12)&15u)>=8 && ((word>>12)&15u)<=10) { kind=XT_LOOP; mask=bit(s); } else if ((word&UINT32_C(0xff000f))==0x090000 || (word&UINT32_C(0xff000f))==0x490000) { if (!(next.ps&ESK_XT_PS_EXCM)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); - kind=9; /* L32E/S32E in exception mode only */ + kind=XT_EXCEPTION_MEMORY; /* L32E/S32E in exception mode only */ } - else if (word==0x002010) kind=7; /* RSYNC; no asynchronous pipeline here */ + else if ((word&UINT32_C(0xffc0ff))==0x400000) { + kind=XT_SAR_SETUP; n=r; mask=bit(s); /* SSR/SSL/SSA8L/SSA8B */ + } else if ((word&UINT32_C(0xfff0ef))==0x404000) { kind=XT_SAR_SETUP; n=4; } + else if ((word&UINT32_C(0xff000f))==0xa10000 && !t) { kind=XT_SAR_SHIFT; n=0; mask=bit(r)|bit(s); } + else if ((word&UINT32_C(0xff000f))==0x910000 && !s) { kind=XT_SAR_SHIFT; n=1; mask=bit(r)|bit(t); } + else if ((word&UINT32_C(0xff000f))==0xb10000 && !s) { kind=XT_SAR_SHIFT; n=2; mask=bit(r)|bit(t); } + else if ((word&UINT32_C(0xff000f))==0x810000) { kind=XT_SAR_SHIFT; n=3; mask=bit(r)|bit(s)|bit(t); } + else if (word==0x0020c0 || word==0x002000 || word==0x002020) kind=XT_FENCE; + else if ((word&UINT32_C(0xfff00f))==0x006000) { kind=XT_RSIL; mask=bit(t); } + else if (word==0x005000) kind=XT_SYSCALL; + else if (word==0 || word==0xf06d) kind=XT_ILLEGAL; + else if (word==0x003000 || word==0x003200) kind=XT_EXCEPTION_RETURN; + else if (word==0x002010) kind=XT_RSYNC; /* RSYNC; no asynchronous pipeline here */ else if (references(code,width,word,&mask)!=ES_OK) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); - if (kind>=4 && kind<=6 && !(kind==6 && ((word>>8)&255u)<=2) && !(next.ps&ESK_XT_PS_EXCM) && ((next.ps>>6)&3u)) - return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); /* privilege dispatch not yet modelled */ + if ((kind==XT_WINDOW_RETURN || kind==XT_ROTW || + (kind==XT_SPECIAL && ((word>>8)&255u)>=64) || kind==XT_RSIL || kind==XT_EXCEPTION_RETURN) && + !(next.ps&ESK_XT_PS_EXCM) && ((next.ps>>6)&3u)) { + if (!next.exceptions_enabled) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + st=esk_xtensa_raise_exception(&next,8,0,0,event); if (st==ES_OK) *w=next; return st; + } e=overflow(&next,mask); if (e) { *w=next; *event=e; return ES_OK; } - if (kind==1) { + if (kind==XT_CALL) { next.ps=(next.ps&~UINT32_C(0x30000)) | (n<<16); next.cpu.a[n*4]=(n<<30)|((pc+3)&UINT32_C(0x3fffffff)); next.cpu.pc=target; flow=0; - } else if (kind==2) { + } else if (kind==XT_ENTRY) { uint32_t sp=next.cpu.a[s]-((word>>12)<<3); next.base=(next.base+n)&15u; next.start|=bit(next.base); next.ar[(next.base*4+s)&63u]=sp; load(&next); next.cpu.pc=pc+3; - } else if (kind==3) { + } else if (kind==XT_RETW) { unsigned back,nearest=0,old=next.base; uint32_t link=next.cpu.a[0],return_pc=(pc&UINT32_C(0xc0000000))|(link&UINT32_C(0x3fffffff)); n=link>>30; @@ -129,36 +198,27 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo } flow=0; /* CALLINC is preserved; the ISA permits implementation-dependent update. */ - } else if (kind==4) { + } else if (kind==XT_WINDOW_RETURN) { if (!(next.ps&ESK_XT_PS_EXCM)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); if (word==0x003400) next.start&=~bit(next.base); else next.start|=bit(next.base); next.base=(next.ps>>8)&15u; next.ps&=~ESK_XT_PS_EXCM; load(&next); next.cpu.pc=next.epc1; flow=0; - } else if (kind==5) { + } else if (kind==XT_ROTW) { next.base=(next.base+t)&15u; /* modulo 16 is identical for signed imm4 */ load(&next); next.cpu.pc=pc+3; - } else if (kind==6) { - unsigned reg=(word>>8)&255u; uint32_t value=next.cpu.a[t]; - int write=(word&UINT32_C(0x100000))!=0; - if (reg>2 && reg!=72 && reg!=73 && reg!=177 && reg!=230) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); - if (write) { - if (reg==0) next.lbeg=value; - else if (reg==1) next.lend=value; - else if (reg==2) next.lcount=value; - else if (reg==72) { next.base=value&15u; load(&next); } - else if (reg==73) next.start=value&65535u; - else if (reg==177) next.epc1=value; - else next.ps=value; - } else next.cpu.a[t]=reg==0 ? next.lbeg : reg==1 ? next.lend : reg==2 ? next.lcount : - reg==72 ? next.base : reg==73 ? next.start : reg==177 ? next.epc1 : next.ps; + } else if (kind==XT_SPECIAL) { + unsigned reg=(word>>8)&255u; uint32_t value=next.cpu.a[t],old=special_read(&next,reg); + uint32_t form=word&UINT32_C(0xff000f); + if (form==0x030000) next.cpu.a[t]=old; + else { special_write(&next,reg,value); if (form==0x610000) next.cpu.a[t]=old; } next.cpu.pc=pc+3; - } else if (kind==8) { + } else if (kind==XT_LOOP) { unsigned condition=(word>>12)&15u; uint32_t count=next.cpu.a[s]; next.lcount=count-1u; next.lbeg=pc+3; next.lend=pc+4+(word>>16); next.cpu.pc=pc+3; if ((condition==9 && !count) || (condition==10 && (!count || (count>>31)))) { next.cpu.pc=next.lend; flow=0; } - } else if (kind==9) { + } else if (kind==XT_EXCEPTION_MEMORY) { uint32_t addr=next.cpu.a[s]+((((word>>12)&15u)-16u)<<2),value=0; int write=(word&UINT32_C(0x400000))!=0; if (addr&3u) return reject(fault,ESK_XT_ALIGN,pc,addr,word); @@ -171,7 +231,30 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo next.cpu.a[t]=value; } next.cpu.pc=pc+3; - } else if (kind==7) next.cpu.pc=pc+3; + } else if (kind==XT_SAR_SETUP) { + uint32_t value=next.cpu.a[s]; + next.sar=n==0 ? value&31u : n==1 ? 32u-(value&31u) : n==2 ? (value&3u)*8u : + n==3 ? 32u-(value&3u)*8u : s|((t&1u)<<4); + next.cpu.pc=pc+3; + } else if (kind==XT_SAR_SHIFT) { + uint64_t joined; + if (next.sar>32) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + if (n==0) joined=(uint64_t)next.cpu.a[s]<<32; + else if (n==1) joined=next.cpu.a[t]; + else if (n==2) joined=(uint64_t)next.cpu.a[t] | ((next.cpu.a[t]>>31) ? UINT64_C(0xffffffff00000000) : 0); + else joined=(uint64_t)next.cpu.a[s]<<32 | next.cpu.a[t]; + next.cpu.a[r]=(uint32_t)(joined>>next.sar); next.cpu.pc=pc+3; + } else if (kind==XT_FENCE) next.cpu.pc=pc+3; /* fully serialized functional bus, no pipeline/cache claim */ + else if (kind==XT_RSIL) { next.cpu.a[t]=next.ps; next.ps=(next.ps&~15u)|s; next.cpu.pc=pc+3; } + else if (kind==XT_SYSCALL || kind==XT_ILLEGAL) { + if (!next.exceptions_enabled) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + st=esk_xtensa_raise_exception(&next,kind==XT_SYSCALL ? 1u : 0u,0,0,event); if (st==ES_OK) *w=next; return st; + } else if (kind==XT_EXCEPTION_RETURN) { + if (!next.exceptions_enabled) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + if (word==0x003000) { next.ps&=~ESK_XT_PS_EXCM; next.cpu.pc=next.epc1; } + else next.cpu.pc=next.depc; + flow=0; + } else if (kind==XT_RSYNC) next.cpu.pc=pc+3; else { st=esk_xtensa_execute_flow(&next.cpu,memory,code,available,consumed,fault,&flow); if (st!=ES_OK) return st; diff --git a/core/mcu/xtensa/windows.h b/core/mcu/xtensa/windows.h index a9a8f7cb4..0e79b20e5 100644 --- a/core/mcu/xtensa/windows.h +++ b/core/mcu/xtensa/windows.h @@ -9,17 +9,27 @@ * cpu.a is the editable visible cache; physical ARs synchronize on successful * instructions/exception transitions. Init configures an ABI test state, not * silicon power-on defaults. Vectors are caller-supplied, not inferred. - * Only PS/WINDOWBASE/WINDOWSTART/EPC1 and loop special registers are implemented. - * Generic exceptions, interrupts, MMU/caches and timing remain unsupported. */ + * Selected special registers, SAR and opt-in XEA2-with-DEPC exception entry + * are implemented. Interrupts, automatic bus/MMU fault dispatch, complete ISA, + * MMU/caches and timing remain unsupported. MEMW/ISYNC/ESYNC only serialize + * the already synchronous functional profile; no asynchronous pipeline claim. */ typedef struct esk_xtensa_windows { esk_xtensa_state cpu; uint32_t ar[64], ps, base, start, epc1, lbeg, lend, lcount; + uint32_t sar, exccause, excvaddr, depc, exceptions_enabled; + uint32_t exception_vectors[3]; /* kernel, user, double; configured explicitly */ uint32_t vectors[6]; /* overflow4, underflow4, overflow8, underflow8, overflow12, underflow12 */ } esk_xtensa_windows; -/* event 0: an instruction retired; 1..6: window exception transition. On a - * window event consumed is zero (retry instruction after spill/fill handler). - * Window exceptions intentionally change state, including base, PS and PC. +/* event 0: retired; 1..6: window transition; 7/8/9: kernel/user/double exception. + * Exception entry consumes zero bytes. Window instructions retry after spill/fill; + * syscall/illegal handlers explicitly decide whether to advance EPC1/DEPC. + * Architectural exception entries intentionally update saved state and PC. * Other failures retain state and return the ordinary fault report. */ +/* Explicit exception profile; no SoC vector addresses inferred. Unknown or + * unimplemented instructions remain unsupported, never silently illegal. */ +void esk_xtensa_exception_configure(esk_xtensa_windows *state, const uint32_t vectors[3]); +es_status esk_xtensa_raise_exception(esk_xtensa_windows *state, unsigned cause, + uint32_t address, int has_address, unsigned *event); void esk_xtensa_windows_init(esk_xtensa_windows *state, uint32_t pc, const uint32_t vectors[6]); es_status esk_xtensa_window_execute(esk_xtensa_windows *state, const esk_xtensa_memory *memory, const unsigned char *code, size_t available, diff --git a/docs/STATUS.md b/docs/STATUS.md index 84f172fd2..9c45d298b 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -21,8 +21,8 @@ window: 111 native assertions and 5,376 separately assembled all-register compar pass. Mixed-width fetch, compact arithmetic, non-windowed calls/returns, conditional branches and callback memory access now execute an assembled summing/subroutine program. The execution test passes 618 assertions; a separate assembler/linker -referee passes 3,840 execution cases and 960 failed accesses. General -exceptions/interrupts, full ISA, SMP, SoC boot and timing remain +referee passes 3,840 execution cases and 960 failed accesses. Complete +exception/interrupt behavior, full ISA, SMP, SoC boot and timing remain unimplemented. The new 64-AR window engine implements windowed calls/ENTRY/RETW, lazy spill/fill exception transitions, RFWO/RFWU retries, selected special registers and hardware loops (1,337 native assertions). The actual Espressif @@ -32,6 +32,11 @@ ESP-IDF's actual spill/fill handler instruction bodies through the bus, passing overflow/underflow counts. This does not establish ESP-IDF boot or SoC fidelity. Additionally, 96 compiler-generated C executions (sum/sort/copy, 33,472 fetched instructions) pass buffer, guard, return and callee-save checks. +SAR setup/variable shifts and exchange, RSIL state and selected opt-in XEA2 +syscall/illegal/privilege/nested entry/return now pass 785 native assertions. +An additional 7,040 real ESP32 compiled bit-manipulation executions pass (37,818 +fetched instructions). Unknown instructions and ambiguous bus failures are not +assigned invented exception causes; general interrupt delivery remains absent. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 3dec03a0f..4698eb4c6 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -156,7 +156,7 @@ Implemented window behavior: spill/fill memory accesses, with natural alignment and failed-access retention. - ROTW and RSR/WSR for PS, WINDOWBASE, WINDOWSTART and EPC1 are supported. Privileged operations reject unsupported user-mode execution. General privilege - exception dispatch is not implemented. PS fields unrelated to these mechanisms + exception dispatch is opt-in as described below. PS fields unrelated to these mechanisms are retained but do not implement interrupts, security or other system behavior. The module preserves CALLINC on RETW, one of the ISA-permitted implementation @@ -222,6 +222,73 @@ vendor handlers perform actual bus loads/stores and RFWO/RFWU retries; no host shadow-register restore is used. General RTOS exception/interrupt code, MOVSP, full context switching, ESP-IDF boot and SoC devices remain outside this evidence. +## Implemented: SAR and selected XEA2 system behavior + +The execution state now includes SAR, EXCCAUSE, EXCVADDR and DEPC. SAR is six +bits wide on WSR/XSR; SSR uses only five source bits, SSL writes 32 minus the +five-bit shift, SSA8L/SSA8B select byte shifts, and SSAI supplies an immediate. +SLL/SRL/SRA/SRC handle the defined 0..32 SAR range without host-language +shift overflow. SAR values above 32 remain readable/writable but executing those +shifts returns unsupported because the ISA leaves the result undefined. XSR.SAR +exchanges the register atomically within this single-instruction functional +model; other XSR special-register forms remain unsupported. + +RSR/WSR support the new fields. SAR and loop registers are accessible without +privileged-register checks; implemented privileged register operations and RSIL +honor the configured PS.RING/EXCM state. RSIL returns old PS and changes +INTLEVEL, but this does not implement pending interrupt delivery or interrupt +masking conformance. MEMW/ISYNC/ESYNC are serialized by the already synchronous +bus and register execution; there is no prefetch/cache/pipeline or asynchronous +external-effect qualification. EXTW is still unsupported. + +An explicit opt-in XEA2-with-DEPC profile configures kernel, user and double +exception vectors. It implements: + +- SYSCALL, known ILL/ILL.N opcodes, and privilege faults for the implemented + privileged instructions. Unimplemented valid instructions (tested with QUOU) + remain unsupported; they are never silently translated to illegal opcodes. +- Kernel versus user selection by PS.UM on initial entry, EPC1 capture and EXCM + entry. A nested exception captures DEPC, preserves EPC1 and selects the double + vector. EXCCAUSE updates; EXCVADDR changes only on explicitly address-bearing + injection, not on an unrelated syscall. +- RFE clearing EXCM and returning via EPC1; RFDE returning via DEPC while + retaining EXCM. Saved PCs name the faulting instruction; a syscall handler + explicitly advances its saved PC if it wants to resume after the instruction. +- An explicit exception-injection API for future configured bus/SoC models. + The caller supplies the architecturally established cause/address semantics. + +The profile is disabled by initialization until vectors are configured. Those +addresses are test/host inputs, not an inferred ESP32 VECBASE or ROM layout. +Generic memory/fetch failures remain diagnostics because the current callback +cannot distinguish protection, translation and bus error causes accurately. +Interrupts, automatic MMU/bus fault dispatch, debug exceptions and complete +privilege/system behavior remain unimplemented. No CPU constructor or board +catalog target is added. + +`test_xtensa_system` passes 785 assertions: shift boundaries, masking/exchange, +window checks on real operands, syscall kernel/user/double selection, saved PC +advance and nested returns, EXCVADDR retention, explicit illegal instructions, +unsupported valid opcodes, privilege faults and synchronous fence acceptance. +A fetched program executes SYSCALL, runs a memory-resident handler that reads +and advances EPC1, returns via RFE, and resumes the next instruction with the +expected result. The existing 224 deep windowed-ABI runs and all six vendor +spill/fill paths still pass with the expanded state. + +`run_system_c.py` uses the actual ESP32 GCC 8.4.0 toolchain to compile unchanged +C left/right/arithmetic shifts, rotation and funnel functions. It passes 7,040 +runs (37,818 fetched instructions), including unsigned extremes, signed negative +inputs, all masked shift counts and 0/32 funnel boundaries. It also checks caller +stack, register-window and return state. These are functional CPU tests, not +hardware timing, interrupt delivery or SoC boot qualification. + +```sh +python tests/vendor/xtensa_integer/run_system_c.py \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld \ + --nm /path/to/xtensa-esp32-elf-nm +``` + ## Implemented: standalone firmware integrity inspection `embsim image-info esp32 app.bin` inspects a plaintext original-ESP32 image diff --git a/tests/unit/test_xtensa_system.c b/tests/unit/test_xtensa_system.c new file mode 100644 index 000000000..f58f5d76c --- /dev/null +++ b/tests/unit/test_xtensa_system.c @@ -0,0 +1,133 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/xtensa/windows.h" + +static const uint32_t vectors[3]={0x40020000,0x40020040,0x40020080}; +static es_status execute(esk_xtensa_windows *w,uint32_t word,unsigned *event) +{ + unsigned char code[3];size_t used=99;esk_xtensa_fault fault;es_status st; + code[0]=(unsigned char)(word&255u);code[1]=(unsigned char)((word>>8)&255u);code[2]=(unsigned char)((word>>16)&255u); + st=esk_xtensa_window_execute(w,NULL,code,3,&used,event,&fault); + CHECK_EQ(used,st==ES_OK && !*event ? 3u : 0u);return st; +} +static void shifts(void) +{ + esk_xtensa_windows w,before;unsigned event,i; + esk_xtensa_windows_init(&w,0x40000000,NULL); + for (i=0;i<32;i++) { + uint32_t value=0x89abcdefu; + w.cpu.a[2]=i;CHECK_EQ(execute(&w,0x401200,&event),ES_OK);CHECK_EQ(w.sar,32u-i); + w.cpu.a[7]=value;CHECK_EQ(execute(&w,0xa16700,&event),ES_OK);CHECK_EQ(w.cpu.a[6],value<>i); + w.cpu.a[11]=value;CHECK_EQ(execute(&w,0xb1a0b0,&event),ES_OK); + CHECK_EQ(w.cpu.a[10],(value>>i)|(i ? UINT32_MAX<<(32u-i) : 0u)); + } + for (i=0;i<=32;i++) { + uint64_t joined=UINT64_C(0x1234567889abcdef); + w.cpu.a[3]=i;CHECK_EQ(execute(&w,0x130330,&event),ES_OK); + w.cpu.a[13]=0x12345678;w.cpu.a[14]=0x89abcdef; + CHECK_EQ(execute(&w,0x81cde0,&event),ES_OK);CHECK_EQ(w.cpu.a[12],(uint32_t)(joined>>i)); + } + w.cpu.a[3]=32;CHECK_EQ(execute(&w,0x130330,&event),ES_OK); + CHECK_EQ(execute(&w,0x918090,&event),ES_OK);CHECK_EQ(w.cpu.a[8],0); + CHECK_EQ(execute(&w,0xb1a0b0,&event),ES_OK);CHECK_EQ(w.cpu.a[10],UINT32_MAX); + CHECK_EQ(execute(&w,0xa16700,&event),ES_OK);CHECK_EQ(w.cpu.a[6],0x89abcdefu); + w.cpu.a[3]=0;CHECK_EQ(execute(&w,0x130330,&event),ES_OK); + CHECK_EQ(execute(&w,0xa16700,&event),ES_OK);CHECK_EQ(w.cpu.a[6],0); + w.cpu.a[3]=127;CHECK_EQ(execute(&w,0x130330,&event),ES_OK);CHECK_EQ(w.sar,63); + before=w;CHECK_EQ(execute(&w,0x918090,&event),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&w,&before,sizeof w),0); + w.cpu.a[4]=64;CHECK_EQ(execute(&w,0x610340,&event),ES_OK);CHECK_EQ(w.sar,0);CHECK_EQ(w.cpu.a[4],63); + CHECK_EQ(execute(&w,0x030320,&event),ES_OK);CHECK_EQ(w.cpu.a[2],0); + w.cpu.a[4]=7;CHECK_EQ(execute(&w,0x402400,&event),ES_OK);CHECK_EQ(w.sar,24); + w.cpu.a[5]=7;CHECK_EQ(execute(&w,0x403500,&event),ES_OK);CHECK_EQ(w.sar,8); + CHECK_EQ(execute(&w,0x404f10,&event),ES_OK);CHECK_EQ(w.sar,31); +} +static void nested_exceptions(void) +{ + esk_xtensa_windows w,before;unsigned event; + esk_xtensa_windows_init(&w,0x40001234,NULL);before=w; + CHECK_EQ(execute(&w,0x005000,&event),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&w,&before,sizeof w),0); + esk_xtensa_exception_configure(&w,vectors);w.excvaddr=0x12345678;w.cpu.a[2]=42; + CHECK_EQ(execute(&w,0x005000,&event),ES_OK);CHECK_EQ(event,7);CHECK_EQ(w.cpu.pc,vectors[0]); + CHECK_EQ(w.epc1,0x40001234u);CHECK_EQ(w.exccause,1);CHECK_EQ(w.excvaddr,0x12345678u); + CHECK_EQ(w.cpu.a[2],42);CHECK_EQ(w.ps&ESK_XT_PS_EXCM,ESK_XT_PS_EXCM); + CHECK_EQ(execute(&w,0x005000,&event),ES_OK);CHECK_EQ(event,9);CHECK_EQ(w.cpu.pc,vectors[2]); + CHECK_EQ(w.depc,vectors[0]);CHECK_EQ(w.epc1,0x40001234u); + w.cpu.a[3]=w.depc+3;CHECK_EQ(execute(&w,0x13c030,&event),ES_OK); /* WSR DEPC */ + CHECK_EQ(execute(&w,0x003200,&event),ES_OK);CHECK_EQ(w.cpu.pc,vectors[0]+3u); + CHECK_EQ(w.ps&ESK_XT_PS_EXCM,ESK_XT_PS_EXCM); + w.cpu.a[6]=w.epc1+3;CHECK_EQ(execute(&w,0x13b160,&event),ES_OK); + CHECK_EQ(execute(&w,0x003000,&event),ES_OK);CHECK_EQ(w.cpu.pc,0x40001237u); + CHECK_EQ(w.ps&ESK_XT_PS_EXCM,0); + w.ps|=32u;CHECK_EQ(execute(&w,0x005000,&event),ES_OK);CHECK_EQ(event,8);CHECK_EQ(w.cpu.pc,vectors[1]); + /* Explicit address-bearing injection preserves EPC1 during a double fault. */ + { uint32_t epc=w.epc1; + CHECK_EQ(esk_xtensa_raise_exception(&w,9,0xdeadbeef,1,&event),ES_OK); + CHECK_EQ(event,9);CHECK_EQ(w.epc1,epc);CHECK_EQ(w.exccause,9);CHECK_EQ(w.excvaddr,0xdeadbeefu); + } + before=w;CHECK_EQ(esk_xtensa_raise_exception(&w,64,0,0,&event),ES_EINVAL);CHECK_EQ(memcmp(&w,&before,sizeof w),0); + before=w;CHECK_EQ(execute(&w,0xc22340,&event),ES_EUNSUPPORTED);CHECK_EQ(event,0); + CHECK_EQ(memcmp(&w,&before,sizeof w),0); /* valid, unimplemented QUOU is not an illegal opcode */ +} +static void privilege_and_sync(void) +{ + esk_xtensa_windows w;unsigned event;uint32_t old; + esk_xtensa_windows_init(&w,0x40000000,NULL);w.ps|=3u;old=w.ps; + CHECK_EQ(execute(&w,0x006750,&event),ES_OK);CHECK_EQ(w.cpu.a[5],old);CHECK_EQ(w.ps&15u,7); + CHECK_EQ(execute(&w,0x0020c0,&event),ES_OK);CHECK_EQ(execute(&w,0x002000,&event),ES_OK); + CHECK_EQ(execute(&w,0x002020,&event),ES_OK); + esk_xtensa_exception_configure(&w,vectors);w.ps|=64u|32u; + old=w.cpu.pc;CHECK_EQ(execute(&w,0x006750,&event),ES_OK);CHECK_EQ(event,8); + CHECK_EQ(w.epc1,old);CHECK_EQ(w.exccause,8);CHECK_EQ(w.cpu.pc,vectors[1]); + /* User SAR access is legal and does not become a privilege exception. */ + w.ps&=~ESK_XT_PS_EXCM;w.cpu.a[3]=5; + CHECK_EQ(execute(&w,0x130330,&event),ES_OK);CHECK_EQ(event,0);CHECK_EQ(w.sar,5); +} +static void operand_windows_and_illegal(void) +{ + esk_xtensa_windows w,before;unsigned event; + static const uint32_t win[6]={0x40010000,0x40010040,0x40010080,0x400100c0,0x40010100,0x40010140}; + esk_xtensa_windows_init(&w,0x40000000,win);w.start=7; + CHECK_EQ(execute(&w,0x404f10,&event),ES_OK);CHECK_EQ(event,0);CHECK_EQ(w.sar,31); + CHECK_EQ(execute(&w,0x401c00,&event),ES_OK);CHECK_EQ(event,1); /* SSL a12 references a high group */ + CHECK_EQ(w.epc1,0x40000003u);CHECK_EQ(w.sar,31); + esk_xtensa_windows_init(&w,0x40001234,NULL);esk_xtensa_exception_configure(&w,vectors); + CHECK_EQ(execute(&w,0,&event),ES_OK);CHECK_EQ(event,7);CHECK_EQ(w.exccause,0);CHECK_EQ(w.epc1,0x40001234u); + CHECK_EQ(execute(&w,0xf06d,&event),ES_OK);CHECK_EQ(event,9);CHECK_EQ(w.exccause,0);CHECK_EQ(w.depc,vectors[0]); + before=w;CHECK_EQ(execute(&w,0xc22340,&event),ES_EUNSUPPORTED);CHECK_EQ(event,0); + CHECK_EQ(memcmp(&w,&before,sizeof w),0); +} +static es_status code_read(void *ctx,uint32_t address,unsigned bytes,uint32_t *value) +{ + const unsigned char *data=(const unsigned char *)ctx;unsigned i;uint32_t offset; + if (address<0x1000 || bytes>4) return ES_EINVAL; + offset=address-0x1000; + if (offset>=256 || bytes>256-offset) return ES_EINVAL; + *value=0;for(i=0;i>(n&31); } +int arithmetic(int v,unsigned n) { return v>>(n&31); } +unsigned rotate(unsigned v,unsigned n) { n&=31; return (v<>((0u-n)&31)); } +unsigned funnel(unsigned hi,unsigned lo,unsigned n) { + if (!n) return lo; + if (n>=32) return hi; + return (hi<<(32-n))|(lo>>n); +} +''' + + +def main(): + ap=argparse.ArgumentParser(description=__doc__) + for name in ['compiler','objcopy','linker','nm']:ap.add_argument('--'+name,required=True) + ap.add_argument('--cc',default='cc');args=ap.parse_args();rng=random.Random(0x5a4) + values=[0,1,0x7fffffff,0x80000000,MASK,0x89abcdef]+[rng.getrandbits(32) for _ in range(16)] + with tempfile.TemporaryDirectory(prefix='embsim-system-c-') as tmp: + out=Path(tmp);src=out/'bits.c';obj=out/'bits.o';elf=out/'bits.elf';binary=out/'bits.bin' + src.write_text(SOURCE,encoding='utf-8') + subprocess.run([args.compiler,'-B'+str(Path(args.compiler).parent)+'/','-O1','-ffreestanding','-fno-builtin','-c',str(src),'-o',str(obj)],check=True) + subprocess.run([args.linker,'--no-relax','-Ttext=0x1000','--entry=left',str(obj),'-o',str(elf)],check=True) + subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(elf),str(binary)],check=True) + symbols={} + for line in subprocess.check_output([args.nm,str(elf)],text=True).splitlines(): + f=line.split() + if len(f)==3:symbols[f[2]]=int(f[0],16) + lib=out/'system.so' + subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC', + '-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'), + *[str(ROOT/'core/mcu/xtensa'/f) for f in ['integer.c','execute.c','windows.c']],'-o',str(lib)],check=True) + api=C.CDLL(str(lib));init=api.esk_xtensa_windows_init + init.argtypes=[C.POINTER(Windows),C.c_uint32,C.POINTER(C.c_uint32)];init.restype=None + step=api.esk_xtensa_window_step;step.argtypes=[C.POINTER(Windows),C.POINTER(Memory),C.POINTER(C.c_uint),C.POINTER(Fault)];step.restype=C.c_int + code=binary.read_bytes() + @Read + def read(ctx,address,size,value): + if not (0x1000<=address and address+size<=0x1000+len(code)):return -1 + value[0]=int.from_bytes(code[address-0x1000:address-0x1000+size],'little');return 0 + @Write + def write(ctx,address,size,value):return -1 + bus=Memory(None,read,write);checks=0;instructions=0 + for name in ['left','right','arithmetic','rotate','funnel']: + for v in values: + for n in range(64): + lo=rng.getrandbits(32);amount=n&31 + if name=='left':expected=v<>amount + elif name=='arithmetic':expected=(v if v<2**31 else v-2**32)>>amount + elif name=='rotate':expected=(v<>((-amount)&31)) + else:expected=lo if n==0 else v if n>=32 else (v<<(32-n))|(lo>>n) + state=Windows();init(C.byref(state),symbols[name],None) + stop=0x3ffffff0;state.ps|=1<<16;state.cpu.a[4]=(1<<30)|stop;state.cpu.a[1]=0x201000 + state.cpu.a[6]=v;state.cpu.a[7]=n + if name=='funnel':state.cpu.a[7]=lo;state.cpu.a[8]=n + event=C.c_uint();fault=Fault();count=0 + while state.cpu.pc!=stop and count<100: + status=step(C.byref(state),C.byref(bus),C.byref(event),C.byref(fault)) + assert status==0 and event.value==0,(name,v,n,hex(fault.pc),hex(fault.opcode),fault.kind,event.value) + count+=1 + assert state.cpu.pc==stop and state.base==0 and state.start==1,(name,'return/window') + assert state.cpu.a[6]==expected&MASK,(name,hex(v),n,hex(state.cpu.a[6]),hex(expected&MASK)) + assert state.cpu.a[1]==0x201000,(name,'stack') + checks+=1;instructions+=count + print(f'{checks} real ESP32 compiled shift/rotate/funnel executions pass; {instructions} fetched instructions') + + +if __name__=='__main__':main() diff --git a/tests/vendor/xtensa_integer/run_windows_c.py b/tests/vendor/xtensa_integer/run_windows_c.py index 4a4a769e4..2f17bcd03 100644 --- a/tests/vendor/xtensa_integer/run_windows_c.py +++ b/tests/vendor/xtensa_integer/run_windows_c.py @@ -17,7 +17,8 @@ class Windows(C.Structure): _fields_=[('cpu',State),('ar',C.c_uint32*64),('ps',C.c_uint32),('base',C.c_uint32), ('start',C.c_uint32),('epc1',C.c_uint32),('lbeg',C.c_uint32),('lend',C.c_uint32), - ('lcount',C.c_uint32),('vectors',C.c_uint32*6)] + ('lcount',C.c_uint32),('sar',C.c_uint32),('exccause',C.c_uint32),('excvaddr',C.c_uint32), + ('depc',C.c_uint32),('exceptions_enabled',C.c_uint32),('exception_vectors',C.c_uint32*3),('vectors',C.c_uint32*6)] NESTED=''' From dec8ec7db999969468bffe20c70326fedf8d756c Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Wed, 4 Nov 2026 22:24:17 +0000 Subject: [PATCH 09/57] Deliver ESP32 CPU interrupts and explicit cycle-counter compare events --- CHANGELOG.md | 13 ++ core/mcu/xtensa/interrupts.c | 126 +++++++++++++ core/mcu/xtensa/interrupts.h | 41 +++++ core/mcu/xtensa/windows.c | 24 ++- core/mcu/xtensa/windows.h | 12 +- docs/STATUS.md | 8 +- docs/engineering/esp32-dual-core.md | 91 ++++++++- tests/unit/test_xtensa_interrupts.c | 174 ++++++++++++++++++ tests/unit/test_xtensa_timers.c | 75 ++++++++ tests/vendor/README.md | 8 + tests/vendor/xtensa_integer/run_interrupts.py | 69 +++++++ tests/vendor/xtensa_integer/run_system_c.py | 2 +- .../vendor/xtensa_integer/run_timer_clock.py | 70 +++++++ tests/vendor/xtensa_integer/run_windows_c.py | 8 +- 14 files changed, 705 insertions(+), 16 deletions(-) create mode 100644 core/mcu/xtensa/interrupts.c create mode 100644 core/mcu/xtensa/interrupts.h create mode 100644 tests/unit/test_xtensa_interrupts.c create mode 100644 tests/unit/test_xtensa_timers.c create mode 100644 tests/vendor/xtensa_integer/run_interrupts.py create mode 100644 tests/vendor/xtensa_integer/run_timer_clock.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c47806338..5c64fd867 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental ESP32 CPU interrupts and compare clock + +- Added an opt-in 32-input ESP32 CPU interrupt profile: fixed levels/types, + INTENABLE/EXCM masking, edge/software pending state, NMI, saved high-level + contexts, RFI, WAITI and special registers. The SoC interrupt matrix and + peripheral routing remain unimplemented; no board target is registered. +- Added CCOUNT/CCOMPARE0..2, sticky timer requests, wrap-safe explicit clock + advances and deadline reporting. No instruction-cycle count is guessed. + WAITI can wake from supplied compare ticks; pipeline/service timing is not + qualified, and a scheduler must split advances at deadlines when needed. +- Added 11,571 interrupt and 81 timer assertions, a fetched acknowledging ISR, + 2,048 vendor-configuration comparisons and 8,192 tick-oracle comparisons. + ### Experimental Xtensa SAR and selected system behavior - Added SAR setup and variable shifts/funnel operations, XSR.SAR, selected system diff --git a/core/mcu/xtensa/interrupts.c b/core/mcu/xtensa/interrupts.c new file mode 100644 index 000000000..63396b8a7 --- /dev/null +++ b/core/mcu/xtensa/interrupts.c @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* Interrupt configuration facts checked against Espressif's ESP32 toolchain + * core-isa.h and XEA2 semantics. Original controller implementation. */ +#include "mcu/xtensa/windows.h" +#include + +#define SOFTWARE UINT32_C(0x20000080) +#define EDGE UINT32_C(0x50400400) +#define NMI UINT32_C(0x00004000) +static const unsigned timer_input[3]={6,15,16}; +static const uint32_t levels[8]={0,0x000637ff,0x00380000,0x28c08800,0x53000000,0x84010000,0,0x00004000}; + +es_status esk_xtensa_irq_configure(struct esk_xtensa_windows *w,const uint32_t high_vectors[6]) +{ + unsigned i; + if (!w || !high_vectors || !w->exceptions_enabled) return ES_EINVAL; + memset(&w->irq,0,sizeof w->irq);w->irq.enabled=1; + for(i=0;i<6;i++) w->irq.vectors[i+2]=high_vectors[i]; + return ES_OK; +} +es_status esk_xtensa_irq_line(struct esk_xtensa_windows *w,unsigned input,int asserted) +{ + uint32_t mask; + if (!w || !w->irq.enabled || input>=32) return ES_EINVAL; + mask=UINT32_C(1)<irq.lines&mask) && (mask&(EDGE|NMI))) w->irq.latched|=mask; + w->irq.lines|=mask; + } else w->irq.lines&=~mask; + return ES_OK; +} +uint32_t esk_xtensa_irq_pending(const struct esk_xtensa_windows *w) +{ + if (!w || !w->irq.enabled) return 0; + return (w->irq.lines&~(SOFTWARE|EDGE|NMI))|(w->irq.latched&(SOFTWARE|EDGE|NMI)); +} +es_status esk_xtensa_irq_poll(struct esk_xtensa_windows *w,unsigned *event) +{ + uint32_t pending,eligible,mask;unsigned threshold,level; + if (event) *event=0; + if (!w || !event) return ES_EINVAL; + if (!w->irq.enabled) return ES_OK; + pending=esk_xtensa_irq_pending(w);threshold=w->ps&15u; + if ((w->ps&ESK_XT_PS_EXCM) && threshold<3) threshold=3; /* ESP32 EXCMLEVEL */ + eligible=pending&w->irq.intenable; + if (pending&NMI) level=7; + else { + for(level=5;level>0;level--) if (level>threshold && (eligible&levels[level])) break; + if (!level) return ES_OK; + } + mask=level==7 ? NMI : eligible&levels[level]; + if (level==1) { + es_status st=esk_xtensa_raise_exception(w,4,0,0,event); + if (st!=ES_OK) return st; + } else { + w->irq.epc[level]=w->cpu.pc;w->irq.eps[level]=w->ps; + w->ps=(w->ps&~15u)|level|ESK_XT_PS_EXCM;w->cpu.pc=w->irq.vectors[level]; + } + if (level==7) w->irq.latched&=~NMI; + w->irq.waiting=0;w->irq.last_level=level;w->irq.last_pending=mask; + *event=10u+level;return ES_OK; +} +es_status esk_xtensa_irq_return(struct esk_xtensa_windows *w,unsigned level) +{ + if (!w || !w->irq.enabled || level<2 || level>7) return ES_EUNSUPPORTED; + w->ps=w->irq.eps[level];w->cpu.pc=w->irq.epc[level];return ES_OK; +} +int esk_xtensa_irq_special(unsigned reg) +{ + return (reg>=178 && reg<=183)||(reg>=194 && reg<=199)||(reg>=209 && reg<=215)||(reg>=226 && reg<=228)||reg==234||(reg>=240 && reg<=242); +} +es_status esk_xtensa_irq_read(const struct esk_xtensa_windows *w,unsigned reg,uint32_t *value) +{ + if (!w || !w->irq.enabled || !value) return ES_EUNSUPPORTED; + if (reg>=178 && reg<=183) *value=w->irq.epc[reg-176]; + else if (reg>=194 && reg<=199) *value=w->irq.eps[reg-192]; + else if (reg>=209 && reg<=215) *value=w->irq.excsave[reg-208]; + else if (reg==226) *value=esk_xtensa_irq_pending(w); + else if (reg==228) *value=w->irq.intenable; + else if (reg==234) *value=w->irq.ccount; + else if (reg>=240 && reg<=242) *value=w->irq.compare[reg-240]; + else return ES_EUNSUPPORTED; + return ES_OK; +} +es_status esk_xtensa_irq_write(struct esk_xtensa_windows *w,unsigned reg,uint32_t value) +{ + if (!w || !w->irq.enabled) return ES_EUNSUPPORTED; + if (reg>=178 && reg<=183) w->irq.epc[reg-176]=value; + else if (reg>=194 && reg<=199) w->irq.eps[reg-192]=value; + else if (reg>=209 && reg<=215) w->irq.excsave[reg-208]=value; + else if (reg==226) w->irq.latched|=value&SOFTWARE; + else if (reg==227) w->irq.latched&=~(value&(SOFTWARE|EDGE)); + else if (reg==228) w->irq.intenable=value; + else if (reg==234) w->irq.ccount=value; + else if (reg>=240 && reg<=242) { + w->irq.compare[reg-240]=value;w->irq.lines&=~(UINT32_C(1)<irq.enabled) return ES_EUNSUPPORTED; + for(i=0;i<3;i++) if(ticks>=distance(w->irq.ccount,w->irq.compare[i])) + w->irq.lines|=UINT32_C(1)<irq.ccount+=(uint32_t)(ticks&UINT64_C(0xffffffff));return ES_OK; +} +int esk_xtensa_clock_deadline(const struct esk_xtensa_windows *w,uint64_t *ticks) +{ + unsigned i;uint64_t best=UINT64_C(0x100000001); + if(ticks) *ticks=0; + if(!w || !w->irq.enabled || !ticks) return 0; + for(i=0;i<3;i++) if(!(w->irq.lines&(UINT32_C(1)<irq.ccount,w->irq.compare[i]);if(dUINT64_C(0x100000000)) return 0; + *ticks=best;return 1; +} diff --git a/core/mcu/xtensa/interrupts.h b/core/mcu/xtensa/interrupts.h new file mode 100644 index 000000000..c4cff40b7 --- /dev/null +++ b/core/mcu/xtensa/interrupts.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_XTENSA_INTERRUPTS_H +#define ESK_XTENSA_INTERRUPTS_H +#include "embsim/types.h" +struct esk_xtensa_windows; +/* ESP32 LX6 CPU input profile. No SoC interrupt matrix, external peripheral + * sources, scheduler latency or electrical pin model. CPU counter/compare + * behavior uses explicitly supplied ticks, never guessed instruction cycles. All transitions occur + * at explicit instruction boundaries. Initial values are test configuration, + * not hardware reset qualification. */ +typedef struct esk_xtensa_interrupts { + uint32_t enabled, intenable, lines, latched, waiting, last_level, last_pending; + uint32_t epc[8], eps[8], excsave[8], vectors[8]; + uint32_t ccount, compare[3]; +} esk_xtensa_interrupts; +/* Configuration resets controller/context state; requires exception profile. + * high_vectors[0..5] are level 2..7 vectors. Level 1 uses kernel/user exception + * vectors. Do not reconfigure a live interrupt context. */ +es_status esk_xtensa_irq_configure(struct esk_xtensa_windows *w,const uint32_t high_vectors[6]); +/* Drive a CPU hardware/internal input. Software inputs 7/29 use INTSET only. + * Edge/NMI inputs latch rising edges; deassert/reassert to generate another. */ +es_status esk_xtensa_irq_line(struct esk_xtensa_windows *w,unsigned input,int asserted); +uint32_t esk_xtensa_irq_pending(const struct esk_xtensa_windows *w); +/* Poll without fetching. event 11..17: accepted level 1..7; zero: none. No + * maskable source is acknowledged automatically. NMI edge is consumed on entry. */ +es_status esk_xtensa_irq_poll(struct esk_xtensa_windows *w,unsigned *event); +es_status esk_xtensa_irq_return(struct esk_xtensa_windows *w,unsigned level); +/* Advance explicitly supplied processor-clock ticks, including WAITI. No ticks + * are guessed from instruction retirement. Bulk advance latches compare hits; + * callers split at deadlines when exact event scheduling matters. */ +es_status esk_xtensa_clock_advance(struct esk_xtensa_windows *w,uint64_t ticks); +/* Earliest unlatched compare, 1..2^32 ticks away; zero when none/profile off. */ +int esk_xtensa_clock_deadline(const struct esk_xtensa_windows *w,uint64_t *ticks); +/* Internal special-register interface for INTERRUPT/INTSET/INTCLEAR/INTENABLE, + * EPC2..7, EPS2..7, EXCSAVE1..7, CCOUNT and CCOMPARE0..2. + * INTCLEAR has no defined read value. */ +int esk_xtensa_irq_special(unsigned reg); +es_status esk_xtensa_irq_read(const struct esk_xtensa_windows *w,unsigned reg,uint32_t *value); +es_status esk_xtensa_irq_write(struct esk_xtensa_windows *w,unsigned reg,uint32_t value); +#endif diff --git a/core/mcu/xtensa/windows.c b/core/mcu/xtensa/windows.c index 1eb5cfdc0..d13365c21 100644 --- a/core/mcu/xtensa/windows.c +++ b/core/mcu/xtensa/windows.c @@ -8,7 +8,7 @@ enum execution_kind { XT_BASE, XT_CALL, XT_ENTRY, XT_RETW, XT_WINDOW_RETURN, XT_ROTW, XT_SPECIAL, XT_RSYNC, XT_LOOP, XT_EXCEPTION_MEMORY, XT_SAR_SETUP=11, XT_SAR_SHIFT, XT_FENCE, XT_RSIL, XT_SYSCALL, - XT_EXCEPTION_RETURN, XT_ILLEGAL + XT_EXCEPTION_RETURN, XT_ILLEGAL, XT_RFI, XT_WAITI }; static uint32_t bit(unsigned n) { return UINT32_C(1) << (n & 15u); } @@ -35,6 +35,7 @@ void esk_xtensa_exception_configure(esk_xtensa_windows *w, const uint32_t vector { if (!w) return; w->exceptions_enabled=vectors ? 1u : 0u; + if (!vectors) { w->irq.enabled=0; w->irq.waiting=0; } if (vectors) memcpy(w->exception_vectors,vectors,sizeof w->exception_vectors); } es_status esk_xtensa_raise_exception(esk_xtensa_windows *w, unsigned cause, @@ -52,12 +53,13 @@ es_status esk_xtensa_raise_exception(esk_xtensa_windows *w, unsigned cause, *event=7u+index; return ES_OK; } -static int special_valid(unsigned reg) +static int special_valid(const esk_xtensa_windows *w,unsigned reg) { - return reg<=3 || reg==72 || reg==73 || reg==177 || reg==192 || reg==230 || reg==232 || reg==238; + return (w->irq.enabled && esk_xtensa_irq_special(reg)) || reg<=3 || reg==72 || reg==73 || reg==177 || reg==192 || reg==230 || reg==232 || reg==238; } static uint32_t special_read(const esk_xtensa_windows *w,unsigned reg) { + if (esk_xtensa_irq_special(reg)) { uint32_t value=0; (void)esk_xtensa_irq_read(w,reg,&value); return value; } switch (reg) { case 0:return w->lbeg; case 1:return w->lend; case 2:return w->lcount; case 3:return w->sar; case 72:return w->base; case 73:return w->start; case 177:return w->epc1; @@ -66,6 +68,7 @@ static uint32_t special_read(const esk_xtensa_windows *w,unsigned reg) } static void special_write(esk_xtensa_windows *w,unsigned reg,uint32_t value) { + if (esk_xtensa_irq_special(reg)) { (void)esk_xtensa_irq_write(w,reg,value); return; } switch (reg) { case 0:w->lbeg=value;break; case 1:w->lend=value;break; case 2:w->lcount=value;break; case 3:w->sar=value&63u;break; @@ -145,7 +148,7 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo else if ((word&UINT32_C(0xffff0f))==0x408000) kind=XT_ROTW; else if ((word&UINT32_C(0xff000f))==0x030000 || (word&UINT32_C(0xff000f))==0x130000 || (word&UINT32_C(0xff000f))==0x610000) { unsigned reg=(word>>8)&255u; - if (!special_valid(reg) || ((word&UINT32_C(0xff000f))==0x610000 && reg!=3)) + if (!special_valid(&next,reg) || (reg==227 && (word&UINT32_C(0xff000f))==0x030000) || ((word&UINT32_C(0xff000f))==0x610000 && reg!=3)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); kind=XT_SPECIAL; mask=bit(t); } @@ -166,10 +169,12 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo else if (word==0x005000) kind=XT_SYSCALL; else if (word==0 || word==0xf06d) kind=XT_ILLEGAL; else if (word==0x003000 || word==0x003200) kind=XT_EXCEPTION_RETURN; + else if ((word&UINT32_C(0xfff0ff))==0x003010 && s>=2 && s<=7 && next.irq.enabled) kind=XT_RFI; + else if ((word&UINT32_C(0xfff0ff))==0x007000 && next.irq.enabled) kind=XT_WAITI; else if (word==0x002010) kind=XT_RSYNC; /* RSYNC; no asynchronous pipeline here */ else if (references(code,width,word,&mask)!=ES_OK) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); if ((kind==XT_WINDOW_RETURN || kind==XT_ROTW || - (kind==XT_SPECIAL && ((word>>8)&255u)>=64) || kind==XT_RSIL || kind==XT_EXCEPTION_RETURN) && + (kind==XT_SPECIAL && ((word>>8)&255u)>=64) || kind==XT_RSIL || kind==XT_EXCEPTION_RETURN || kind==XT_RFI || kind==XT_WAITI) && !(next.ps&ESK_XT_PS_EXCM) && ((next.ps>>6)&3u)) { if (!next.exceptions_enabled) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); st=esk_xtensa_raise_exception(&next,8,0,0,event); if (st==ES_OK) *w=next; return st; @@ -254,6 +259,10 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo if (word==0x003000) { next.ps&=~ESK_XT_PS_EXCM; next.cpu.pc=next.epc1; } else next.cpu.pc=next.depc; flow=0; + } else if (kind==XT_RFI) { + st=esk_xtensa_irq_return(&next,s); if (st!=ES_OK) return st; flow=0; + } else if (kind==XT_WAITI) { + next.ps=(next.ps&~15u)|s; next.cpu.pc=pc+3; next.irq.waiting=1; flow=0; } else if (kind==XT_RSYNC) next.cpu.pc=pc+3; else { st=esk_xtensa_execute_flow(&next.cpu,memory,code,available,consumed,fault,&flow); @@ -271,6 +280,11 @@ es_status esk_xtensa_window_step(esk_xtensa_windows *w, const esk_xtensa_memory if (event) *event=0; if (fault) memset(fault,0,sizeof *fault); if (!w || !event) return ES_EINVAL; + if (w->irq.enabled) { + es_status st=esk_xtensa_irq_poll(w,event); + if (st!=ES_OK || *event) return st; + if (w->irq.waiting) { *event=18; return ES_OK; } + } for (i=0;icpu.pc+i; if (!memory || !memory->read || memory->read(memory->ctx,address,1,&value)!=ES_OK) diff --git a/core/mcu/xtensa/windows.h b/core/mcu/xtensa/windows.h index 0e79b20e5..a1dc49588 100644 --- a/core/mcu/xtensa/windows.h +++ b/core/mcu/xtensa/windows.h @@ -3,6 +3,7 @@ #ifndef ESK_XTENSA_WINDOWS_H #define ESK_XTENSA_WINDOWS_H #include "mcu/xtensa/execute.h" +#include "mcu/xtensa/interrupts.h" #define ESK_XT_PS_EXCM UINT32_C(0x10) #define ESK_XT_PS_WOE UINT32_C(0x40000) /* Experimental LX6 64-AR register-window execution, NOT SoC support. @@ -10,17 +11,20 @@ * instructions/exception transitions. Init configures an ABI test state, not * silicon power-on defaults. Vectors are caller-supplied, not inferred. * Selected special registers, SAR and opt-in XEA2-with-DEPC exception entry - * are implemented. Interrupts, automatic bus/MMU fault dispatch, complete ISA, - * MMU/caches and timing remain unsupported. MEMW/ISYNC/ESYNC only serialize + * are implemented. CPU interrupt/timer delivery is opt-in; the SoC interrupt + * matrix, automatic bus/MMU fault dispatch, complete ISA, MMU/caches and + * instruction timing remain unsupported. MEMW/ISYNC/ESYNC only serialize * the already synchronous functional profile; no asynchronous pipeline claim. */ typedef struct esk_xtensa_windows { esk_xtensa_state cpu; uint32_t ar[64], ps, base, start, epc1, lbeg, lend, lcount; uint32_t sar, exccause, excvaddr, depc, exceptions_enabled; uint32_t exception_vectors[3]; /* kernel, user, double; configured explicitly */ + esk_xtensa_interrupts irq; uint32_t vectors[6]; /* overflow4, underflow4, overflow8, underflow8, overflow12, underflow12 */ } esk_xtensa_windows; -/* event 0: retired; 1..6: window transition; 7/8/9: kernel/user/double exception. +/* event 0: retired; 1..6: window transition; 7/8/9: kernel/user/double exception; + * 11..17: accepted interrupt level 1..7; 18: suspended WAITI (step only). * Exception entry consumes zero bytes. Window instructions retry after spill/fill; * syscall/illegal handlers explicitly decide whether to advance EPC1/DEPC. * Architectural exception entries intentionally update saved state and PC. @@ -34,6 +38,8 @@ void esk_xtensa_windows_init(esk_xtensa_windows *state, uint32_t pc, const uint3 es_status esk_xtensa_window_execute(esk_xtensa_windows *state, const esk_xtensa_memory *memory, const unsigned char *code, size_t available, size_t *consumed, unsigned *event, esk_xtensa_fault *fault); +/* Poll configured CPU interrupts before fetch. event 18 means suspended WAITI + * and no fetch/retirement; explicit clock/input advances can make it runnable. */ es_status esk_xtensa_window_step(esk_xtensa_windows *state, const esk_xtensa_memory *memory, unsigned *event, esk_xtensa_fault *fault); #endif diff --git a/docs/STATUS.md b/docs/STATUS.md index 9c45d298b..d9cef2857 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -36,7 +36,13 @@ SAR setup/variable shifts and exchange, RSIL state and selected opt-in XEA2 syscall/illegal/privilege/nested entry/return now pass 785 native assertions. An additional 7,040 real ESP32 compiled bit-manipulation executions pass (37,818 fetched instructions). Unknown instructions and ambiguous bus failures are not -assigned invented exception causes; general interrupt delivery remains absent. +assigned invented exception causes. The optional CPU interrupt profile now adds +32-input priority/type handling, masks, NMI edges, saved high-level contexts, +RFI and WAITI suspension/wake (11,571 native assertions). CCOUNT/CCOMPARE0..2 +use explicit supplied ticks, including WAITI wake and wraparound (81 assertions). +Referees pass 2,048 vendor-configuration interrupt comparisons and 8,192 +clock/deadline comparisons against 263,719 individual ticks. The SoC interrupt +matrix, peripheral routing, scheduler timing, SMP and board boot remain absent. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 4698eb4c6..3df82c5e8 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -236,8 +236,8 @@ model; other XSR special-register forms remain unsupported. RSR/WSR support the new fields. SAR and loop registers are accessible without privileged-register checks; implemented privileged register operations and RSIL honor the configured PS.RING/EXCM state. RSIL returns old PS and changes -INTLEVEL, but this does not implement pending interrupt delivery or interrupt -masking conformance. MEMW/ISYNC/ESYNC are serialized by the already synchronous +INTLEVEL. The optional CPU input profile below adds functional interrupt masking +and delivery; it does not establish cycle-exact interrupt latency. MEMW/ISYNC/ESYNC are serialized by the already synchronous bus and register execution; there is no prefetch/cache/pipeline or asynchronous external-effect qualification. EXTW is still unsupported. @@ -261,8 +261,9 @@ The profile is disabled by initialization until vectors are configured. Those addresses are test/host inputs, not an inferred ESP32 VECBASE or ROM layout. Generic memory/fetch failures remain diagnostics because the current callback cannot distinguish protection, translation and bus error causes accurately. -Interrupts, automatic MMU/bus fault dispatch, debug exceptions and complete -privilege/system behavior remain unimplemented. No CPU constructor or board +Automatic MMU/bus fault dispatch, debug exceptions and complete +privilege/system behavior remain unimplemented. CPU interrupts are supported only +with the separate explicit profile below. No CPU constructor or board catalog target is added. `test_xtensa_system` passes 785 assertions: shift boundaries, masking/exchange, @@ -289,6 +290,88 @@ python tests/vendor/xtensa_integer/run_system_c.py \ --nm /path/to/xtensa-esp32-elf-nm ``` +## Implemented: ESP32 CPU interrupts and explicit compare-clock ticks + +`interrupts.c` implements the configured ESP32 LX6 CPU input controller. The +32 input levels/types and EXCMLEVEL=3 are checked against Espressif's toolchain +`xtensa/config/core-isa.h`, not inferred from the C3 or an arbitrary priority map. +Expected configuration SHA-256: +`60d20f76c7730b24041b9f55b16e0062f30bf85c7661c7ed215f050fedcdb344`. +This external test input comes from the already hash-verified ESP32 toolchain. +Its permissive vendor configuration notice is not a dependency of the app; +no vendor controller implementation is copied or linked. + +The interrupt profile requires configured general exception vectors and separate +caller-supplied level 2..7 vectors. It is disabled by initialization. Configuration +resets controller/context values for deterministic test setup, not a claim about +silicon reset values. Disabling the general exception profile also disables the +interrupt profile and releases its simulated WAITI suspension. + +- Level inputs remain pending while asserted; external edges latch rising edges + until INTCLEAR. Software inputs 7/29 are set only through INTSET, and other + INTSET bits have no effect. Internal timer/profiling inputs can be driven by + the device input API; that does not make them physical GPIOs. +- Maskable delivery uses INTENABLE and the higher of PS.INTLEVEL and the EXCM + floor of 3. The highest eligible level wins. A vector represents a level, + not an invented winning input number; software can inspect all pending bits. +- The NMI input 14 latches an edge, bypasses masking and consumes that edge on + acceptance. A held input does not create repeated edges. Sampling/glitch timing + and precise silicon NMI status-read timing remain unqualified. +- Level 1 enters the configured general exception path with cause 4. Higher + levels save EPCn/EPSn, set INTLEVEL/EXCM and enter the configured vector. + RFI 2..7 restores the saved PC and PS. No general registers are automatically + pushed to a host shadow stack; handler code saves what it needs. +- INTERRUPT/INTSET, INTCLEAR, INTENABLE, EPC2..7, EPS2..7 and EXCSAVE1..7 are + available through the implemented special-register instructions. INTCLEAR has + no invented read value. Delivery does not automatically acknowledge a maskable + source, so held levels/rejected acknowledgement can immediately retrigger. +- WAITI updates the mask and saves the next PC before suspension. Step returns + event 18 without fetching while no eligible input exists. Pending-before-WAITI + races, level masking and post-WAITI return addresses are checked. This does + not model sleep current, regulators, radio power management or clock gating. + +CCOUNT/CCOMPARE0..2 use **explicit processor-clock advances** through +`esk_xtensa_clock_advance`; instruction retirement does not fabricate elapsed +cycles. Compare hits latch CPU inputs 6/15/16 and remain pending until the +corresponding CCOMPARE write. INTCLEAR and CCOUNT writes do not acknowledge them. +The counter wraps at 32 bits, with zero/multiple-wrap/64-bit tick advances +supported. `esk_xtensa_clock_deadline` returns the nearest unlatched compare, +including a full wrap when compare equals the current count. + +Clock advances continue during WAITI, allowing a compare event to wake the CPU. +Bulk advances capture hits but do not execute handlers midway through the advance; +a host scheduler must split at deadlines when interrupt service timing matters. +Software reads/writes are applied at the functional boundary; their precise +silicon clock/pipeline phase is not qualified. CCOUNT/CCOMPARE are architecturally +undefined at reset, so deterministic zero initialization is only test setup. +No instruction timing, clock-frequency integration or cycle-accurate latency is +claimed until the real core/runtime scheduler is connected. + +The native interrupt test passes 11,571 assertions: all 32 inputs across 16 mask +levels, EXCM on/off and enable on/off, nesting, reassertion, edges/software/NMI, +WAITI races, register/privilege behavior and a fetched handler. That handler saves +A2 in EXCSAVE3, acknowledges software input 29, restores A2 and returns via RFI; +the interrupted program resumes with its expected result. The timer test passes +81 assertions covering deadlines, sticky requests, compare acknowledgement, +wraps/huge advances, CCOUNT register access and timed WAITI wake-up. + +Optional referees provide 2,048 comparisons against the vendor's input level/type +configuration, and 8,192 clock/deadline comparisons against 263,719 individual +oracle ticks with vendor timer-input mapping. The earlier 224 windowed-ABI runs +and 7,040 compiled bit-manipulation runs still pass with the expanded state. + +```sh +python tests/vendor/xtensa_integer/run_interrupts.py \ + --core-isa /path/to/xtensa-esp32-elf/include/xtensa/config/core-isa.h +python tests/vendor/xtensa_integer/run_timer_clock.py \ + --core-isa /path/to/xtensa-esp32-elf/include/xtensa/config/core-isa.h +``` + +This is not the SoC interrupt matrix, peripheral source routing, cross-core +interrupt generation, full ISR/RTOS context switching, debug/NMI cycle timing or +board power behavior. No executable board target is registered. Those remain +separate qualification gates rather than being inferred from CPU input tests. + ## Implemented: standalone firmware integrity inspection `embsim image-info esp32 app.bin` inspects a plaintext original-ESP32 image diff --git a/tests/unit/test_xtensa_interrupts.c b/tests/unit/test_xtensa_interrupts.c new file mode 100644 index 000000000..e82bbcaa3 --- /dev/null +++ b/tests/unit/test_xtensa_interrupts.c @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/xtensa/windows.h" + +static const uint32_t general[3]={0x40010000,0x40010040,0x40010080}; +static const uint32_t high[6]={0x40020000,0x40020040,0x40020080,0x400200c0,0x40020100,0x40020140}; +static const unsigned input_level[32]={1,1,1,1,1,1,1,1,1,1,1,3,1,1,7,3,5,1,1,2,2,2,3,3,4,4,5,3,4,3,4,5}; +static void init(esk_xtensa_windows *w) +{ + esk_xtensa_windows_init(w,0x40001234,NULL);esk_xtensa_exception_configure(w,general); + CHECK_EQ(esk_xtensa_irq_configure(w,high),ES_OK); +} +static es_status execute(esk_xtensa_windows *w,uint32_t word,unsigned *event) +{ + unsigned char code[3];size_t used=99;esk_xtensa_fault f;es_status st; + code[0]=(unsigned char)(word&255u);code[1]=(unsigned char)((word>>8)&255u);code[2]=(unsigned char)((word>>16)&255u); + st=esk_xtensa_window_execute(w,NULL,code,3,&used,event,&f); + CHECK_EQ(used,st==ES_OK && !*event ? 3u : 0u);return st; +} +static void drive(esk_xtensa_windows *w,unsigned input) +{ + if(input==7 || input==29) CHECK_EQ(esk_xtensa_irq_write(w,226,UINT32_C(1)<effective)) { + CHECK_EQ(event,10u+level);CHECK_EQ(w.irq.last_level,level);CHECK_EQ(w.irq.last_pending,mask); + CHECK_EQ(w.cpu.pc,level==1 ? general[0] : high[level-2]); + CHECK_EQ(w.ps&ESK_XT_PS_EXCM,ESK_XT_PS_EXCM); + if(level==1) { CHECK_EQ(w.epc1,0x40001234u);CHECK_EQ(w.exccause,4); } + else { + CHECK_EQ(w.irq.epc[level],0x40001234u);CHECK_EQ(w.irq.eps[level],old_ps); + CHECK_EQ(w.ps&15u,level); + } + CHECK_EQ(esk_xtensa_irq_pending(&w),input==14 ? 0u : mask); + } else { CHECK_EQ(event,0);CHECK_EQ(memcmp(&w,&before,sizeof w),0); } + } +} +static void pending_and_edges(void) +{ + esk_xtensa_windows w,before;unsigned event;uint32_t value; + init(&w); + CHECK_EQ(esk_xtensa_irq_write(&w,226,UINT32_MAX),ES_OK); + CHECK_EQ(esk_xtensa_irq_pending(&w),0x20000080u); + CHECK_EQ(esk_xtensa_irq_write(&w,227,UINT32_MAX),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(&w),0); + CHECK_EQ(esk_xtensa_irq_line(&w,0,1),ES_OK);CHECK_EQ(esk_xtensa_irq_line(&w,6,1),ES_OK); + CHECK_EQ(esk_xtensa_irq_write(&w,227,UINT32_MAX),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(&w),65); + CHECK_EQ(esk_xtensa_irq_line(&w,0,0),ES_OK);CHECK_EQ(esk_xtensa_irq_line(&w,6,0),ES_OK); + CHECK_EQ(esk_xtensa_irq_line(&w,10,1),ES_OK);CHECK_EQ(esk_xtensa_irq_line(&w,10,0),ES_OK); + CHECK_EQ(esk_xtensa_irq_pending(&w),1024);CHECK_EQ(esk_xtensa_irq_write(&w,227,1024),ES_OK); + CHECK_EQ(esk_xtensa_irq_pending(&w),0); + CHECK_EQ(esk_xtensa_irq_line(&w,10,1),ES_OK);CHECK_EQ(esk_xtensa_irq_write(&w,227,1024),ES_OK); + CHECK_EQ(esk_xtensa_irq_line(&w,10,1),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(&w),0); + CHECK_EQ(esk_xtensa_irq_line(&w,10,0),ES_OK);CHECK_EQ(esk_xtensa_irq_line(&w,10,1),ES_OK); + CHECK_EQ(esk_xtensa_irq_pending(&w),1024); + before=w;CHECK_EQ(esk_xtensa_irq_line(&w,7,1),ES_EINVAL);CHECK_EQ(esk_xtensa_irq_line(&w,32,1),ES_EINVAL); + CHECK_EQ(memcmp(&w,&before,sizeof w),0); + CHECK_EQ(esk_xtensa_irq_read(&w,227,&value),ES_EUNSUPPORTED); + before=w;CHECK_EQ(execute(&w,0x03e320,&event),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&w,&before,sizeof w),0); + /* Same-level vector does not invent a winning input number. */ + init(&w);w.irq.intenable=(1u<<19)|(1u<<20);drive(&w,19);drive(&w,20); + CHECK_EQ(esk_xtensa_irq_poll(&w,&event),ES_OK);CHECK_EQ(event,12);CHECK_EQ(w.irq.last_pending,(1u<<19)|(1u<<20)); + CHECK_EQ(esk_xtensa_irq_line(&w,19,0),ES_OK);CHECK_EQ(execute(&w,0x003210,&event),ES_OK); + CHECK_EQ(esk_xtensa_irq_poll(&w,&event),ES_OK);CHECK_EQ(event,12);CHECK_EQ(w.irq.last_pending,1u<<20); +} +static void nesting_and_nmi(void) +{ + esk_xtensa_windows w;unsigned event;uint32_t root,ps; + init(&w);root=w.cpu.pc;ps=w.ps;w.irq.intenable=(1u<<0)|(1u<<24)|(1u<<31); + drive(&w,0);CHECK_EQ(esk_xtensa_irq_poll(&w,&event),ES_OK);CHECK_EQ(event,11);CHECK_EQ(w.epc1,root); + drive(&w,24);CHECK_EQ(esk_xtensa_irq_poll(&w,&event),ES_OK);CHECK_EQ(event,14);CHECK_EQ(w.irq.epc[4],general[0]); + drive(&w,31);CHECK_EQ(esk_xtensa_irq_poll(&w,&event),ES_OK);CHECK_EQ(event,15);CHECK_EQ(w.irq.epc[5],high[2]); + drive(&w,14);CHECK_EQ(esk_xtensa_irq_poll(&w,&event),ES_OK);CHECK_EQ(event,17);CHECK_EQ(w.irq.epc[7],high[3]); + CHECK_EQ(esk_xtensa_irq_poll(&w,&event),ES_OK);CHECK_EQ(event,0); /* held NMI edge not repeated */ + CHECK_EQ(execute(&w,0x003710,&event),ES_OK);CHECK_EQ(w.cpu.pc,high[3]);CHECK_EQ(w.ps&15u,5); + CHECK_EQ(esk_xtensa_irq_line(&w,31,0),ES_OK);CHECK_EQ(execute(&w,0x003510,&event),ES_OK); + CHECK_EQ(w.cpu.pc,high[2]);CHECK_EQ(w.ps&15u,4); + CHECK_EQ(esk_xtensa_irq_line(&w,24,0),ES_OK);CHECK_EQ(execute(&w,0x003410,&event),ES_OK); + CHECK_EQ(w.cpu.pc,general[0]);CHECK_EQ(w.ps&ESK_XT_PS_EXCM,ESK_XT_PS_EXCM); + CHECK_EQ(esk_xtensa_irq_line(&w,0,0),ES_OK);CHECK_EQ(execute(&w,0x003000,&event),ES_OK); + CHECK_EQ(w.cpu.pc,root);CHECK_EQ(w.ps,ps); + /* User level-1 entry follows the configured user exception vector. */ + init(&w);w.ps|=32u;w.irq.intenable=1;drive(&w,0); + CHECK_EQ(esk_xtensa_irq_poll(&w,&event),ES_OK);CHECK_EQ(event,11);CHECK_EQ(w.cpu.pc,general[1]); +} +static void wait_and_registers(void) +{ + esk_xtensa_windows w,before;unsigned event;esk_xtensa_fault f; + init(&w);CHECK_EQ(execute(&w,0x007100,&event),ES_OK); /* WAITI 1 */ + CHECK_EQ(w.cpu.pc,0x40001237u);CHECK_EQ(w.irq.waiting,1); + CHECK_EQ(esk_xtensa_window_step(&w,NULL,&event,&f),ES_OK);CHECK_EQ(event,18);CHECK_EQ(w.cpu.pc,0x40001237u); + w.irq.intenable=1u|(1u<<19);drive(&w,0); + CHECK_EQ(esk_xtensa_window_step(&w,NULL,&event,&f),ES_OK);CHECK_EQ(event,18); + drive(&w,19);CHECK_EQ(esk_xtensa_window_step(&w,NULL,&event,&f),ES_OK);CHECK_EQ(event,12); + CHECK_EQ(w.irq.epc[2],0x40001237u);CHECK_EQ(w.irq.waiting,0); + CHECK_EQ(esk_xtensa_irq_line(&w,19,0),ES_OK);CHECK_EQ(execute(&w,0x003210,&event),ES_OK); + CHECK_EQ(w.cpu.pc,0x40001237u);CHECK_EQ(w.ps&15u,1); + /* Pending-before-WAITI input is not lost when masking is lowered. */ + init(&w);w.ps|=15u;w.irq.intenable=1;drive(&w,0); + CHECK_EQ(esk_xtensa_irq_poll(&w,&event),ES_OK);CHECK_EQ(event,0); + CHECK_EQ(execute(&w,0x007000,&event),ES_OK); + CHECK_EQ(esk_xtensa_window_step(&w,NULL,&event,&f),ES_OK);CHECK_EQ(event,11);CHECK_EQ(w.epc1,0x40001237u); + init(&w);w.cpu.a[3]=0x20000080; + CHECK_EQ(execute(&w,0x13e230,&event),ES_OK); /* INTSET */ + CHECK_EQ(execute(&w,0x03e220,&event),ES_OK);CHECK_EQ(w.cpu.a[2],0x20000080u); + w.cpu.a[6]=0x20000080;CHECK_EQ(execute(&w,0x13e460,&event),ES_OK);CHECK_EQ(w.irq.intenable,0x20000080u); + CHECK_EQ(esk_xtensa_irq_poll(&w,&event),ES_OK);CHECK_EQ(event,13); /* SW 29 is level 3 */ + w.cpu.a[4]=1u<<29;CHECK_EQ(execute(&w,0x13e340,&event),ES_OK); + CHECK_EQ(execute(&w,0x003310,&event),ES_OK);CHECK_EQ(esk_xtensa_irq_poll(&w,&event),ES_OK);CHECK_EQ(event,11); + /* Scratch/high context registers and undefined RFI levels. */ + init(&w);w.cpu.a[3]=0x12345678;CHECK_EQ(execute(&w,0x13c230,&event),ES_OK); + CHECK_EQ(w.irq.eps[2],0x12345678u);CHECK_EQ(execute(&w,0x03c220,&event),ES_OK);CHECK_EQ(w.cpu.a[2],0x12345678u); + before=w;CHECK_EQ(execute(&w,0x003110,&event),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&w,&before,sizeof w),0); +} +static es_status code_read(void *ctx,uint32_t address,unsigned bytes,uint32_t *value) +{ + const unsigned char *data=(const unsigned char *)ctx;unsigned i;uint32_t offset; + if(address<0x1000 || bytes>4) return ES_EINVAL; + offset=address-0x1000; + if(offset>=512 || bytes>512-offset) return ES_EINVAL; + *value=0;for(i=0;i>8)&255u);code[2]=(unsigned char)((word>>16)&255u); + return esk_xtensa_window_execute(w,NULL,code,3,&used,event,&fault); +} +static void deadlines_and_clear(void) +{ + esk_xtensa_windows w;uint64_t ticks;unsigned event;uint32_t value; + init(&w);CHECK_EQ(esk_xtensa_irq_write(&w,240,5),ES_OK);CHECK_EQ(esk_xtensa_irq_write(&w,241,8),ES_OK); + CHECK_EQ(esk_xtensa_irq_write(&w,242,12),ES_OK);CHECK_EQ(esk_xtensa_clock_deadline(&w,&ticks),1);CHECK_EQ(ticks,5); + CHECK_EQ(esk_xtensa_clock_advance(&w,0),ES_OK);CHECK_EQ(w.irq.ccount,0); + CHECK_EQ(esk_xtensa_clock_advance(&w,4),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(&w),0); + CHECK_EQ(esk_xtensa_clock_deadline(&w,&ticks),1);CHECK_EQ(ticks,1); + CHECK_EQ(esk_xtensa_clock_advance(&w,1),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(&w),1u<<6); + CHECK_EQ(esk_xtensa_irq_write(&w,227,UINT32_MAX),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(&w),1u<<6); + CHECK_EQ(esk_xtensa_clock_deadline(&w,&ticks),1);CHECK_EQ(ticks,3); + CHECK_EQ(esk_xtensa_clock_advance(&w,7),ES_OK);CHECK_EQ(w.irq.ccount,12); + CHECK_EQ(esk_xtensa_irq_pending(&w),(1u<<6)|(1u<<15)|(1u<<16)); + CHECK_EQ(esk_xtensa_clock_deadline(&w,&ticks),0);CHECK_EQ(ticks,0); + w.irq.intenable=UINT32_MAX;CHECK_EQ(esk_xtensa_irq_poll(&w,&event),ES_OK);CHECK_EQ(event,15); + CHECK_EQ(w.irq.last_pending,1u<<16);CHECK_EQ(esk_xtensa_irq_write(&w,242,20),ES_OK); + CHECK_EQ(esk_xtensa_irq_pending(&w),(1u<<6)|(1u<<15)); + CHECK_EQ(esk_xtensa_irq_read(&w,234,&value),ES_OK);CHECK_EQ(value,12); + CHECK_EQ(esk_xtensa_irq_read(&w,242,&value),ES_OK);CHECK_EQ(value,20); + /* The register path performs the same acknowledgement. */ + w.cpu.a[3]=30;CHECK_EQ(execute(&w,0x13f130,&event),ES_OK);CHECK_EQ(w.irq.compare[1],30); + CHECK_EQ(esk_xtensa_irq_pending(&w),1u<<6); +} +static void wraps(void) +{ + esk_xtensa_windows w;uint64_t ticks; + init(&w);CHECK_EQ(esk_xtensa_irq_write(&w,234,0xfffffffcu),ES_OK); + CHECK_EQ(esk_xtensa_irq_write(&w,240,0),ES_OK);CHECK_EQ(esk_xtensa_irq_write(&w,241,1),ES_OK); + CHECK_EQ(esk_xtensa_irq_write(&w,242,0xfffffffcu),ES_OK); + CHECK_EQ(esk_xtensa_clock_deadline(&w,&ticks),1);CHECK_EQ(ticks,4); + CHECK_EQ(esk_xtensa_clock_advance(&w,4),ES_OK);CHECK_EQ(w.irq.ccount,0);CHECK_EQ(esk_xtensa_irq_pending(&w),1u<<6); + CHECK_EQ(esk_xtensa_clock_advance(&w,1),ES_OK);CHECK_EQ(w.irq.ccount,1);CHECK_EQ(esk_xtensa_irq_pending(&w),(1u<<6)|(1u<<15)); + CHECK_EQ(esk_xtensa_irq_write(&w,234,7),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(&w),(1u<<6)|(1u<<15)); + CHECK_EQ(esk_xtensa_irq_write(&w,240,7),ES_OK);CHECK_EQ(esk_xtensa_irq_write(&w,241,7),ES_OK); + CHECK_EQ(esk_xtensa_irq_write(&w,242,7),ES_OK);CHECK_EQ(esk_xtensa_clock_deadline(&w,&ticks),1); + CHECK_EQ(ticks,UINT64_C(0x100000000));CHECK_EQ(esk_xtensa_clock_advance(&w,ticks-1),ES_OK); + CHECK_EQ(esk_xtensa_irq_pending(&w),0);CHECK_EQ(esk_xtensa_clock_advance(&w,1),ES_OK); + CHECK_EQ(w.irq.ccount,7);CHECK_EQ(esk_xtensa_irq_pending(&w),(1u<<6)|(1u<<15)|(1u<<16)); + CHECK_EQ(esk_xtensa_clock_advance(&w,UINT64_MAX),ES_OK);CHECK_EQ(w.irq.ccount,6); +} +static void wait_clock_and_registers(void) +{ + esk_xtensa_windows w;unsigned event;esk_xtensa_fault fault; + init(&w);w.irq.intenable=1u<<6;CHECK_EQ(esk_xtensa_irq_write(&w,240,10),ES_OK); + CHECK_EQ(execute(&w,0x007000,&event),ES_OK);CHECK_EQ(w.irq.waiting,1);CHECK_EQ(w.irq.ccount,0); + CHECK_EQ(esk_xtensa_clock_advance(&w,9),ES_OK);CHECK_EQ(esk_xtensa_window_step(&w,NULL,&event,&fault),ES_OK); + CHECK_EQ(event,18);CHECK_EQ(w.irq.ccount,9); + CHECK_EQ(esk_xtensa_clock_advance(&w,1),ES_OK);CHECK_EQ(esk_xtensa_window_step(&w,NULL,&event,&fault),ES_OK); + CHECK_EQ(event,11);CHECK_EQ(w.epc1,0x40001237u);CHECK_EQ(w.irq.waiting,0); + w.cpu.a[3]=0x12345678;CHECK_EQ(execute(&w,0x13ea30,&event),ES_OK); /* WSR CCOUNT */ + CHECK_EQ(w.irq.ccount,0x12345678u);CHECK_EQ(execute(&w,0x03ea20,&event),ES_OK);CHECK_EQ(w.cpu.a[2],0x12345678u); + CHECK_EQ(esk_xtensa_irq_pending(&w),1u<<6); /* CCOUNT write does not acknowledge timer */ +} +TEST_MAIN_BEGIN + (void)t_str_eq; + RUN(deadlines_and_clear);RUN(wraps);RUN(wait_clock_and_registers); +TEST_MAIN_END diff --git a/tests/vendor/README.md b/tests/vendor/README.md index bcf88a2e5..86ca0d278 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -61,3 +61,11 @@ rotation and funnel runs (37,818 fetched instructions), checking functional bit results and caller stack/window/return state. Supply `--compiler`, `--objcopy`, `--linker` and `--nm` as for the windowed runner. This is not interrupt, cache, pipeline or hardware timing qualification. + +`xtensa_integer/run_interrupts.py` validates the hash-pinned actual ESP32 +`core-isa.h` and checks 2,048 input type/level/mask combinations. +`run_timer_clock.py` uses its timer-input mapping for 8,192 counter/deadline +comparisons against 263,719 individual oracle ticks. Both take `--core-isa PATH` +and compile the original C modules with the host compiler; the vendor header +is external reference data, not linked into the app. These checks do not qualify +SoC routing, instruction timing, NMI sampling or interrupt-service latency. diff --git a/tests/vendor/xtensa_integer/run_interrupts.py b/tests/vendor/xtensa_integer/run_interrupts.py new file mode 100644 index 000000000..7ac9ca649 --- /dev/null +++ b/tests/vendor/xtensa_integer/run_interrupts.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""Compare CPU input behavior with hash-pinned Espressif core configuration facts. + +The vendor header is an external test input; no vendor implementation is copied +or linked. This does not qualify SoC routing, peripheral timing or NMI sampling. +""" +import argparse +import ctypes as C +import hashlib +from pathlib import Path +import re +import subprocess +import tempfile +from run_execute import ROOT +from run_windows_c import Windows + + +def main(): + ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('--core-isa',type=Path,required=True) + ap.add_argument('--cc',default='cc');args=ap.parse_args();raw=args.core_isa.read_bytes() + assert hashlib.sha256(raw).hexdigest()=='60d20f76c7730b24041b9f55b16e0062f30bf85c7661c7ed215f050fedcdb344','Unexpected ESP32 core configuration' + text=raw.decode('utf-8') + levels={int(n):int(level) for n,level in re.findall(r'#define\s+XCHAL_INT(\d+)_LEVEL\s+(\d+)',text)} + kinds={int(n):kind for n,kind in re.findall(r'#define\s+XCHAL_INT(\d+)_TYPE\s+XTHAL_INTTYPE_(\w+)',text)} + floor=int(re.search(r'#define\s+XCHAL_EXCM_LEVEL\s+(\d+)',text)[1]) + assert set(levels)==set(kinds)==set(range(32)) + with tempfile.TemporaryDirectory(prefix='embsim-irq-profile-') as tmp: + lib=Path(tmp)/'irq.so' + subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC', + '-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'), + *[str(ROOT/'core/mcu/xtensa'/f) for f in ['integer.c','execute.c','windows.c','interrupts.c']],'-o',str(lib)],check=True) + api=C.CDLL(str(lib));wp=C.POINTER(Windows) + api.esk_xtensa_windows_init.argtypes=[wp,C.c_uint32,C.POINTER(C.c_uint32)];api.esk_xtensa_windows_init.restype=None + api.esk_xtensa_exception_configure.argtypes=[wp,C.POINTER(C.c_uint32)];api.esk_xtensa_exception_configure.restype=None + api.esk_xtensa_irq_configure.argtypes=[wp,C.POINTER(C.c_uint32)];api.esk_xtensa_irq_configure.restype=C.c_int + api.esk_xtensa_irq_write.argtypes=[wp,C.c_uint,C.c_uint32];api.esk_xtensa_irq_write.restype=C.c_int + api.esk_xtensa_irq_line.argtypes=[wp,C.c_uint,C.c_int];api.esk_xtensa_irq_line.restype=C.c_int + api.esk_xtensa_irq_poll.argtypes=[wp,C.POINTER(C.c_uint)];api.esk_xtensa_irq_poll.restype=C.c_int + api.esk_xtensa_irq_pending.argtypes=[wp];api.esk_xtensa_irq_pending.restype=C.c_uint32 + general=(C.c_uint32*3)(0x5000,0x5040,0x5080);high=(C.c_uint32*6)(*[0x6000+i*64 for i in range(6)]) + checks=0 + for source in range(32): + for threshold in range(16): + for excm in [False,True]: + for enable in [False,True]: + w=Windows();p=C.byref(w) + api.esk_xtensa_windows_init(p,0x1000,None);api.esk_xtensa_exception_configure(p,general) + assert api.esk_xtensa_irq_configure(p,high)==0 + w.ps|=threshold|(16 if excm else 0);old_ps=w.ps;mask=1<effective) + event=C.c_uint();assert api.esk_xtensa_irq_poll(p,C.byref(event))==0 + if accepted: + level=levels[source] + assert event.value==10+level and w.irq.last_level==level and w.irq.last_pending==mask,(source,threshold,excm,enable,event.value) + assert w.cpu.pc==(general[0] if level==1 else high[level-2]) + if level==1:assert w.epc1==0x1000 and w.exccause==4 + else:assert w.irq.epc[level]==0x1000 and w.irq.eps[level]==old_ps + else:assert event.value==0 and w.cpu.pc==0x1000 and w.ps==old_ps + checks+=1 + print(f'{checks} interrupt mask/type/level comparisons pass against Espressif core-isa.h') + + +if __name__=='__main__':main() diff --git a/tests/vendor/xtensa_integer/run_system_c.py b/tests/vendor/xtensa_integer/run_system_c.py index 70a97ad37..e831e93ac 100644 --- a/tests/vendor/xtensa_integer/run_system_c.py +++ b/tests/vendor/xtensa_integer/run_system_c.py @@ -45,7 +45,7 @@ def main(): lib=out/'system.so' subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC', '-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'), - *[str(ROOT/'core/mcu/xtensa'/f) for f in ['integer.c','execute.c','windows.c']],'-o',str(lib)],check=True) + *[str(ROOT/'core/mcu/xtensa'/f) for f in ['integer.c','execute.c','windows.c','interrupts.c']],'-o',str(lib)],check=True) api=C.CDLL(str(lib));init=api.esk_xtensa_windows_init init.argtypes=[C.POINTER(Windows),C.c_uint32,C.POINTER(C.c_uint32)];init.restype=None step=api.esk_xtensa_window_step;step.argtypes=[C.POINTER(Windows),C.POINTER(Memory),C.POINTER(C.c_uint),C.POINTER(Fault)];step.restype=C.c_int diff --git a/tests/vendor/xtensa_integer/run_timer_clock.py b/tests/vendor/xtensa_integer/run_timer_clock.py new file mode 100644 index 000000000..6a8bee6c5 --- /dev/null +++ b/tests/vendor/xtensa_integer/run_timer_clock.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""Check compare-clock arithmetic against a tick-by-tick oracle and vendor mapping. + +Clock ticks are supplied explicitly. No instruction timing or silicon read/write +phase accuracy is inferred from this test. +""" +import argparse +import ctypes as C +import hashlib +from pathlib import Path +import random +import re +import subprocess +import tempfile +from run_execute import ROOT, MASK +from run_windows_c import Windows + + +def main(): + ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('--core-isa',type=Path,required=True) + ap.add_argument('--cc',default='cc');args=ap.parse_args();raw=args.core_isa.read_bytes() + assert hashlib.sha256(raw).hexdigest()=='60d20f76c7730b24041b9f55b16e0062f30bf85c7661c7ed215f050fedcdb344' + mapping={int(n):int(source) for n,source in re.findall(r'#define\s+XCHAL_TIMER([0-2])_INTERRUPT\s+(\d+)',raw.decode('utf-8'))} + assert set(mapping)=={0,1,2};rng=random.Random(0xc0c0) + with tempfile.TemporaryDirectory(prefix='embsim-compare-clock-') as tmp: + lib=Path(tmp)/'clock.so' + subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC', + '-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'), + *[str(ROOT/'core/mcu/xtensa'/f) for f in ['integer.c','execute.c','windows.c','interrupts.c']],'-o',str(lib)],check=True) + api=C.CDLL(str(lib));wp=C.POINTER(Windows) + api.esk_xtensa_windows_init.argtypes=[wp,C.c_uint32,C.POINTER(C.c_uint32)];api.esk_xtensa_windows_init.restype=None + api.esk_xtensa_exception_configure.argtypes=[wp,C.POINTER(C.c_uint32)];api.esk_xtensa_exception_configure.restype=None + api.esk_xtensa_irq_configure.argtypes=[wp,C.POINTER(C.c_uint32)];api.esk_xtensa_irq_configure.restype=C.c_int + api.esk_xtensa_irq_write.argtypes=[wp,C.c_uint,C.c_uint32];api.esk_xtensa_irq_write.restype=C.c_int + api.esk_xtensa_irq_pending.argtypes=[wp];api.esk_xtensa_irq_pending.restype=C.c_uint32 + api.esk_xtensa_clock_advance.argtypes=[wp,C.c_uint64];api.esk_xtensa_clock_advance.restype=C.c_int + api.esk_xtensa_clock_deadline.argtypes=[wp,C.POINTER(C.c_uint64)];api.esk_xtensa_clock_deadline.restype=C.c_int + general=(C.c_uint32*3)(0x5000,0x5040,0x5080);high=(C.c_uint32*6)(*[0x6000+i*64 for i in range(6)]) + checks=0;ticks_checked=0 + for trial in range(256): + w=Windows();p=C.byref(w);api.esk_xtensa_windows_init(p,0x1000,None);api.esk_xtensa_exception_configure(p,general) + assert api.esk_xtensa_irq_configure(p,high)==0 + now=(MASK-rng.randrange(64)) if trial%2 else rng.getrandbits(32) + assert api.esk_xtensa_irq_write(p,234,now)==0 + compares=[(now+rng.randrange(33))&MASK for _ in range(3)];pending=[False]*3 + for i,value in enumerate(compares):assert api.esk_xtensa_irq_write(p,240+i,value)==0 + for step in range(32): + if rng.randrange(4)==0: + i=rng.randrange(3);compares[i]=(now+rng.randrange(33))&MASK;pending[i]=False + assert api.esk_xtensa_irq_write(p,240+i,compares[i])==0 + delta=rng.randrange(65) + for tick in range(delta): + now=(now+1)&MASK + for i in range(3): + if now==compares[i]:pending[i]=True + ticks_checked+=delta + assert api.esk_xtensa_clock_advance(p,delta)==0 + assert w.irq.ccount==now,(trial,step,'count') + expected=sum(1< Date: Wed, 4 Nov 2026 22:48:19 +0000 Subject: [PATCH 10/57] Execute Xtensa conditional stores and extended integer synchronization primitives --- CHANGELOG.md | 13 ++ core/mcu/xtensa/execute.c | 25 +++- core/mcu/xtensa/execute.h | 10 +- core/mcu/xtensa/integer.c | 38 +++++- core/mcu/xtensa/integer.h | 5 +- core/mcu/xtensa/windows.c | 26 +++- core/mcu/xtensa/windows.h | 2 +- docs/STATUS.md | 9 +- docs/engineering/esp32-dual-core.md | 104 +++++++++++++--- tests/unit/test_xtensa_arithmetic.c | 81 +++++++++++++ tests/unit/test_xtensa_atomic.c | 79 ++++++++++++ tests/unit/test_xtensa_execute.c | 4 +- tests/unit/test_xtensa_interrupts.c | 2 +- tests/unit/test_xtensa_system.c | 8 +- tests/unit/test_xtensa_windows.c | 2 +- tests/vendor/README.md | 15 ++- tests/vendor/xtensa_integer/run.py | 36 +++++- tests/vendor/xtensa_integer/run_atomic_c.py | 121 +++++++++++++++++++ tests/vendor/xtensa_integer/run_execute.py | 18 ++- tests/vendor/xtensa_integer/run_windows_c.py | 2 +- 20 files changed, 549 insertions(+), 51 deletions(-) create mode 100644 tests/unit/test_xtensa_arithmetic.c create mode 100644 tests/unit/test_xtensa_atomic.c create mode 100644 tests/vendor/xtensa_integer/run_atomic_c.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c64fd867..4cc6e571b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental Xtensa integer and conditional-store extension + +- Added conditional moves, signed/unsigned min/max, high products, division and + remainder, SEXT/normalization and little-endian bit branches. Divide faults + preserve registers and dispatch cause 6 only with a configured exception + profile; unknown valid opcodes remain unsupported. +- Added SCOMPARE1 and S32C1I via one required indivisible bus callback, with + no read/write fallback. Full SMP, shared SoC RAM/coherence, memory-type policy + and implementation-dependent spurious failures remain unqualified. +- Added 128 arithmetic and 67 atomic assertions; expanded assembler cases to + 9,728 integer and 4,608 flow/bit branches. Actual ESP32 compiled atomics/division + pass 832 executions, including 64 interleaved pairs forcing CAS retry. + ### Experimental ESP32 CPU interrupts and compare clock - Added an opt-in 32-input ESP32 CPU interrupt profile: fixed levels/types, diff --git a/core/mcu/xtensa/execute.c b/core/mcu/xtensa/execute.c index 0241a4479..d92e8b5e0 100644 --- a/core/mcu/xtensa/execute.c +++ b/core/mcu/xtensa/execute.c @@ -38,6 +38,8 @@ static es_status execute_inner(esk_xtensa_state *state, const esk_xtensa_memory next = *state; next.pc = pc + width; st = esk_xtensa_integer_execute(next.a, code, available, &used); if (st == ES_OK) { next.pc = pc + (uint32_t)used; width = (unsigned)used; } + else if (st==ES_EINVAL && (word&UINT32_C(0x0f000f))==0x020000 && (word>>20)>=12) + return fail(fault,ESK_XT_DIV_ZERO,pc,pc,word); else if (word == 0x0020f0 || word == 0xf03d) { /* NOP / NOP.N */ } else if (word == 0x000080 || word == 0xf00d) { next.pc = next.a[0]; flow = 0; } /* RET/RET.N */ else if ((word & UINT32_C(0xfff0ff)) == 0x0000a0) { next.pc = next.a[s]; flow = 0; } /* JX */ @@ -61,11 +63,24 @@ static es_status execute_inner(esk_xtensa_state *state, const esk_xtensa_memory taken = t == 2 ? left == right : t == 6 ? left != right : (t == 10 || t == 11) ? left < right : left >= right; if (taken) { next.pc = pc + 4 + sx(word >> 16, 8); flow = 0; } - } else if (op == 7 && (r == 1 || r == 9 || r == 2 || r == 10 || r == 3 || r == 11)) { - uint32_t left = next.a[s], right = next.a[t]; int taken; - if (r == 2 || r == 10) { left ^= UINT32_C(0x80000000); right ^= UINT32_C(0x80000000); } - taken = r == 1 ? left == right : r == 9 ? left != right : - (r == 2 || r == 3) ? left < right : left >= right; + } else if (op == 7) { + uint32_t left=next.a[s],right=next.a[t];int taken=0; + switch(r) { + case 0:taken=(left&right)==0;break; + case 1:taken=left==right;break; + case 2:taken=(left^UINT32_C(0x80000000))<(right^UINT32_C(0x80000000));break; + case 3:taken=left>(right&31u))&1u)==0;break; + case 6:case 7:taken=((left>>(t|((r&1u)<<4)))&1u)==0;break; + case 8:taken=(left&right)!=0;break; + case 9:taken=left!=right;break; + case 10:taken=(left^UINT32_C(0x80000000))>=(right^UINT32_C(0x80000000));break; + case 11:taken=left>=right;break; + case 12:taken=(left&right)!=right;break; + case 13:taken=((left>>(right&31u))&1u)!=0;break; + case 14:case 15:taken=((left>>(t|((r&1u)<<4)))&1u)!=0;break; + } if (taken) { next.pc = pc + 4 + sx(word >> 16, 8); flow = 0; } } else if (op == 12 && t >= 8) { int taken = t < 12 ? next.a[s] == 0 : next.a[s] != 0; diff --git a/core/mcu/xtensa/execute.h b/core/mcu/xtensa/execute.h index 222172f00..6055b8b22 100644 --- a/core/mcu/xtensa/execute.h +++ b/core/mcu/xtensa/execute.h @@ -8,15 +8,21 @@ * registers, exceptions/interrupts, windows, timing or SMP. Faults are returned * to the caller with PC/address/opcode, not dispatched to architectural vectors. * Sequential callbacks provide ordering but make no multicore ordering claim. - * Callback failure MUST mean no completed write. No rollback of MMIO reads. */ + * Callback failure MUST mean no completed write. No rollback of MMIO reads. + * Callbacks must not re-enter or mutate the executing CPU state. Queue its + * input/clock changes for instruction boundaries; these APIs are not thread-safe. */ typedef struct esk_xtensa_state { uint32_t a[16], pc; } esk_xtensa_state; typedef struct esk_xtensa_memory { void *ctx; es_status (*read)(void *ctx, uint32_t address, unsigned bytes, uint32_t *value); es_status (*write)(void *ctx, uint32_t address, unsigned bytes, uint32_t value); + /* Must be one indivisible conditional exchange shared by all bus agents. + * Return original memory in observed on success, whether matched or not. + * On failure no write may complete. No read/write fallback is allowed. */ + es_status (*compare_exchange)(void *ctx,uint32_t address,uint32_t expected,uint32_t desired,uint32_t *observed); } esk_xtensa_memory; enum { ESK_XT_OK, ESK_XT_UNSUPPORTED, ESK_XT_TRUNCATED, ESK_XT_FETCH, - ESK_XT_ALIGN, ESK_XT_LOAD, ESK_XT_STORE }; + ESK_XT_ALIGN, ESK_XT_LOAD, ESK_XT_STORE, ESK_XT_DIV_ZERO, ESK_XT_ATOMIC }; typedef struct esk_xtensa_fault { unsigned kind; uint32_t pc, address, opcode; } esk_xtensa_fault; /* Execute supplied instruction bytes; no state commit on failure. */ es_status esk_xtensa_execute(esk_xtensa_state *state, const esk_xtensa_memory *memory, diff --git a/core/mcu/xtensa/integer.c b/core/mcu/xtensa/integer.c index ec2a7b466..3e6b8351a 100644 --- a/core/mcu/xtensa/integer.c +++ b/core/mcu/xtensa/integer.c @@ -10,6 +10,17 @@ static uint32_t extend(uint32_t x, unsigned bits) uint32_t sign = UINT32_C(1) << (bits - 1); return (x ^ sign) - sign; } +static int64_t signed_value(uint32_t value) +{ + return (int64_t)value-((value>>31) ? INT64_C(4294967296) : 0); +} +static uint32_t leading_zeroes(uint32_t value) +{ + uint32_t n=0; + if(!value) return 32; + while(!(value&UINT32_C(0x80000000))) { n++;value<<=1; } + return n; +} static es_status execute_inner(uint32_t a[16], const unsigned char *code, size_t available, size_t *consumed, uint32_t *references) { @@ -64,6 +75,30 @@ static es_status execute_inner(uint32_t a[16], const unsigned char *code, unsigned shift = s + (op1 & 1u) * 16u; if (shift + op2 + 1 > 32) return ES_EUNSUPPORTED; result = (a[t] >> shift) & ((UINT32_C(1) << (op2 + 1)) - 1u); mask = 1u << t; + } else if (op == 0 && op1 == 3 && op2 >= 8 && op2 <= 11) { + int yes=op2==8 ? a[t]==0 : op2==9 ? a[t]!=0 : op2==10 ? (a[t]>>31)!=0 : (a[t]>>31)==0; + result=yes ? a[s] : a[r];mask=(1u<= 4 && op2 <= 7) { + uint32_t left=a[s],right=a[t];int less; + if(op2<6) { left^=UINT32_C(0x80000000);right^=UINT32_C(0x80000000); } + less=left>31)) value=~value; + result=leading_zeroes(value);if(r==14) result--; + } else if (op == 0 && op1 == 2 && (op2 == 10 || op2 == 11)) { + uint64_t product=op2==10 ? (uint64_t)a[s]*a[t] : (uint64_t)(signed_value(a[s])*signed_value(a[t])); + result=(uint32_t)(product>>32);mask=(1u<= 12) { + mask=(1u<irq.enabled && esk_xtensa_irq_special(reg)) || reg<=3 || reg==72 || reg==73 || reg==177 || reg==192 || reg==230 || reg==232 || reg==238; + return (w->irq.enabled && esk_xtensa_irq_special(reg)) || reg<=3 || reg==12 || reg==72 || reg==73 || reg==177 || reg==192 || reg==230 || reg==232 || reg==238; } static uint32_t special_read(const esk_xtensa_windows *w,unsigned reg) { if (esk_xtensa_irq_special(reg)) { uint32_t value=0; (void)esk_xtensa_irq_read(w,reg,&value); return value; } switch (reg) { case 0:return w->lbeg; case 1:return w->lend; case 2:return w->lcount; case 3:return w->sar; - case 72:return w->base; case 73:return w->start; case 177:return w->epc1; + case 12:return w->scompare1; case 72:return w->base; case 73:return w->start; case 177:return w->epc1; case 192:return w->depc; case 230:return w->ps; case 232:return w->exccause; default:return w->excvaddr; } } @@ -72,6 +72,7 @@ static void special_write(esk_xtensa_windows *w,unsigned reg,uint32_t value) switch (reg) { case 0:w->lbeg=value;break; case 1:w->lend=value;break; case 2:w->lcount=value;break; case 3:w->sar=value&63u;break; + case 12:w->scompare1=value;break; case 72:w->base=value&15u;load(w);break; case 73:w->start=value&65535u;break; case 177:w->epc1=value;break; case 192:w->depc=value;break; @@ -92,7 +93,7 @@ static es_status references(const unsigned char *code, unsigned width, uint32_t else if ((word&63u)==5) *mask=1; else if ((word&63u)==6) return ES_OK; else if (op==6 && ((t&3u)==1 || t==2 || t==6 || t==10 || t==14 || t==11 || t==15)) *mask=bit(s); - else if (op==7 && (r==1 || r==9 || r==2 || r==10 || r==3 || r==11)) *mask=bit(s)|bit(t); + else if (op==7) *mask=(r==6 || r==7 || r==14 || r==15) ? bit(s) : bit(s)|bit(t); else if (op==12 && t>=8) *mask=bit(s); else if (op==8 || op==9 || (op==2 && (r<=2 || r==4 || r==5 || r==6 || r==9))) *mask=bit(s)|bit(t); else if (op==1) *mask=bit(t); @@ -148,7 +149,7 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo else if ((word&UINT32_C(0xffff0f))==0x408000) kind=XT_ROTW; else if ((word&UINT32_C(0xff000f))==0x030000 || (word&UINT32_C(0xff000f))==0x130000 || (word&UINT32_C(0xff000f))==0x610000) { unsigned reg=(word>>8)&255u; - if (!special_valid(&next,reg) || (reg==227 && (word&UINT32_C(0xff000f))==0x030000) || ((word&UINT32_C(0xff000f))==0x610000 && reg!=3)) + if (!special_valid(&next,reg) || (reg==227 && (word&UINT32_C(0xff000f))==0x030000) || ((word&UINT32_C(0xff000f))==0x610000 && reg!=3 && reg!=12)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); kind=XT_SPECIAL; mask=bit(t); } @@ -171,6 +172,8 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo else if (word==0x003000 || word==0x003200) kind=XT_EXCEPTION_RETURN; else if ((word&UINT32_C(0xfff0ff))==0x003010 && s>=2 && s<=7 && next.irq.enabled) kind=XT_RFI; else if ((word&UINT32_C(0xfff0ff))==0x007000 && next.irq.enabled) kind=XT_WAITI; + else if (op==2 && r==14) { kind=XT_ATOMIC;mask=bit(s)|bit(t); } + else if ((word&UINT32_C(0x0f000f))==0x020000 && (word>>20)>=12) { kind=XT_DIVIDE;mask=bit(r)|bit(s)|bit(t); } else if (word==0x002010) kind=XT_RSYNC; /* RSYNC; no asynchronous pipeline here */ else if (references(code,width,word,&mask)!=ES_OK) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); if ((kind==XT_WINDOW_RETURN || kind==XT_ROTW || @@ -259,6 +262,19 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo if (word==0x003000) { next.ps&=~ESK_XT_PS_EXCM; next.cpu.pc=next.epc1; } else next.cpu.pc=next.depc; flow=0; + } else if (kind==XT_ATOMIC) { + uint32_t address=next.cpu.a[s]+((word>>16)<<2),observed=0; + if(address&3u) return reject(fault,ESK_XT_ALIGN,pc,address,word); + if(!memory || !memory->compare_exchange) return reject(fault,ESK_XT_UNSUPPORTED,pc,address,word); + st=memory->compare_exchange(memory->ctx,address,next.scompare1,next.cpu.a[t],&observed); + if(st!=ES_OK) return reject(fault,st==ES_EUNSUPPORTED ? ESK_XT_UNSUPPORTED : ESK_XT_ATOMIC,pc,address,word); + next.cpu.a[t]=observed;next.cpu.pc=pc+3; + } else if (kind==XT_DIVIDE && !next.cpu.a[t]) { + if(!next.exceptions_enabled) return reject(fault,ESK_XT_DIV_ZERO,pc,pc,word); + st=esk_xtensa_raise_exception(&next,6,0,0,event);if(st==ES_OK) *w=next;return st; + } else if (kind==XT_DIVIDE) { + st=esk_xtensa_execute_flow(&next.cpu,memory,code,available,consumed,fault,&flow); + if(st!=ES_OK) return st; } else if (kind==XT_RFI) { st=esk_xtensa_irq_return(&next,s); if (st!=ES_OK) return st; flow=0; } else if (kind==XT_WAITI) { diff --git a/core/mcu/xtensa/windows.h b/core/mcu/xtensa/windows.h index a1dc49588..160f65df9 100644 --- a/core/mcu/xtensa/windows.h +++ b/core/mcu/xtensa/windows.h @@ -18,7 +18,7 @@ typedef struct esk_xtensa_windows { esk_xtensa_state cpu; uint32_t ar[64], ps, base, start, epc1, lbeg, lend, lcount; - uint32_t sar, exccause, excvaddr, depc, exceptions_enabled; + uint32_t sar, exccause, excvaddr, depc, scompare1, exceptions_enabled; uint32_t exception_vectors[3]; /* kernel, user, double; configured explicitly */ esk_xtensa_interrupts irq; uint32_t vectors[6]; /* overflow4, underflow4, overflow8, underflow8, overflow12, underflow12 */ diff --git a/docs/STATUS.md b/docs/STATUS.md index d9cef2857..337399a9f 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -17,11 +17,11 @@ Native `image-info esp32` now validates standalone image headers, segments, XOR checksum and optional SHA-256, with explicit rejection of signed/encrypted/merged images. An experimental, unregistered Xtensa integer kernel now executes the wide arithmetic/logic/immediate/low-multiply subset against a supplied register -window: 111 native assertions and 5,376 separately assembled all-register comparisons +window: 111 native assertions and 9,728 separately assembled all-register comparisons pass. Mixed-width fetch, compact arithmetic, non-windowed calls/returns, conditional branches and callback memory access now execute an assembled summing/subroutine program. The execution test passes 618 assertions; a separate assembler/linker -referee passes 3,840 execution cases and 960 failed accesses. Complete +referee passes 4,608 execution cases and 960 failed accesses. Complete exception/interrupt behavior, full ISA, SMP, SoC boot and timing remain unimplemented. The new 64-AR window engine implements windowed calls/ENTRY/RETW, lazy spill/fill exception transitions, RFWO/RFWU retries, selected special @@ -43,6 +43,11 @@ use explicit supplied ticks, including WAITI wake and wraparound (81 assertions) Referees pass 2,048 vendor-configuration interrupt comparisons and 8,192 clock/deadline comparisons against 263,719 individual ticks. The SoC interrupt matrix, peripheral routing, scheduler timing, SMP and board boot remain absent. +Conditional moves/min/max/high products/division/normalization and bit branches +now pass 128 additional native assertions. SCOMPARE1/S32C1I require one indivisible +bus callback, with no non-atomic fallback (67 assertions). Another 832 actual +ESP32 compiled atomic/division runs pass, including 64 interleaved increment +pairs that force retry. This does not establish a shared SoC bus or full SMP. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 3df82c5e8..c1d6babf5 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -43,24 +43,27 @@ register; successful execution reports three consumed bytes. The integer helper last uses Xtensa's asymmetric -32..95 immediate range. This helper has no register-window rotation, interrupts, architectural exception dispatch, special registers, FPU, SMP or timing model. -It is not wired into the CPU constructor or catalog and cannot run firmware. -The caller-provided window is not a claim that register-window behavior exists. +It is not wired into a CPU constructor or board catalog; the app cannot boot +firmware on this target yet. This integer helper alone does not rotate windows; +the higher-level engine below owns the physical register file. `test_xtensa_integer` checks an assembled sequence, arithmetic extremes, aliases, input errors and unchanged state on unsupported execution (111 assertions). -`tests/vendor/xtensa_integer/run.py` independently assembles 5,376 instruction +`tests/vendor/xtensa_integer/run.py` independently assembles 9,728 instruction cases with GNU binutils and compares every output register against Python integer arithmetic. It varies register aliases, immediates and 32-bit extremes -with a fixed seed. GNU xtensa-lx106 binutils 2.40 was used solely as an external -encoding referee for the shared subset; it is not an LX6 system emulator or an -ESP32 qualification suite. No assembler code or decoder tables are copied. +with a fixed seed. The expanded runner uses the actual Espressif ESP32 tools +for the additional +ISA options. Earlier LX106 binutils checks covered only the shared base subset; +LX106 is not a substitute for the newly tested LX6 options or an ESP32 system +qualification suite. No assembler code or decoder tables are copied. Reproduce on Linux with a host C compiler and separate Xtensa binutils: ```sh python tests/vendor/xtensa_integer/run.py \ - --assembler /path/to/xtensa-lx106-elf-as \ - --objcopy /path/to/xtensa-lx106-elf-objcopy + --assembler /path/to/xtensa-esp32-elf-as \ + --objcopy /path/to/xtensa-esp32-elf-objcopy ``` The deterministic native unit test runs in CTest without those external tools. @@ -98,16 +101,16 @@ loop, calls a doubling subroutine and returns to the expected PC (27 executed instructions). The native test has 618 assertions covering branches, compact immediate limits, byte/halfword/word behavior, literal loads, link-register alias, 32-bit PC wrap, memory-edge fetches, alignment and failed-access state retention. -An optional external-assembler/linker referee checks 3,840 execution cases and +An optional external-assembler/linker referee checks 4,608 execution cases and 960 rejected accesses against independent Python PC/register/bus expectations. Linker relaxation is disabled to preserve the fixture's explicit instruction -addresses. The earlier 5,376 integer cases continue to pass. +addresses. The earlier 9,728 integer cases continue to pass. ```sh python tests/vendor/xtensa_integer/run_execute.py \ - --assembler /path/to/xtensa-lx106-elf-as \ - --objcopy /path/to/xtensa-lx106-elf-objcopy \ - --linker /path/to/xtensa-lx106-elf-ld + --assembler /path/to/xtensa-esp32-elf-as \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld ``` A further compiler acceptance runner compiles unchanged freestanding C for @@ -120,8 +123,8 @@ windowed-ABI, hardware or vendor SDK qualification. ```sh python tests/vendor/xtensa_integer/run_c.py \ --compiler /path/to/xtensa-lx106-elf-gcc \ - --objcopy /path/to/xtensa-lx106-elf-objcopy \ - --linker /path/to/xtensa-lx106-elf-ld \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld \ --nm /path/to/xtensa-lx106-elf-nm ``` @@ -245,7 +248,7 @@ An explicit opt-in XEA2-with-DEPC profile configures kernel, user and double exception vectors. It implements: - SYSCALL, known ILL/ILL.N opcodes, and privilege faults for the implemented - privileged instructions. Unimplemented valid instructions (tested with QUOU) + privileged instructions. Unimplemented valid instructions (tested with ADD.S) remain unsupported; they are never silently translated to illegal opcodes. - Kernel versus user selection by PS.UM on initial entry, EPC1 capture and EXCM entry. A nested exception captures DEPC, preserves EPC1 and selects the double @@ -372,6 +375,75 @@ interrupt generation, full ISR/RTOS context switching, debug/NMI cycle timing or board power behavior. No executable board target is registered. Those remain separate qualification gates rather than being inferred from CPU input tests. +## Implemented: extended integer operations, bit branches and conditional store + +The integer kernel now adds MOVEQZ/MOVNEZ/MOVLTZ/MOVGEZ, signed/unsigned +MIN/MAX, MULUH/MULSH, QUOU/QUOS/REMU/REMS, SEXT and NSA/NSAU. Conditional +moves retain the old destination when not taken; register aliases are supported. +Signed division truncates toward zero and remainder follows the numerator's +sign. INT_MIN/-1 produces the architectural wrapped quotient (and zero remainder) +without invoking host C overflow; compiled C tests exclude that undefined C +expression and the native instruction fixture tests it directly. + +Divide-by-zero preserves the destination and faults before instruction retirement. +The configured XEA2 profile dispatches cause 6; without it, execution returns a +specific divide diagnostic. Register-window exceptions retain their higher +priority. Unsupported valid FPU instructions (ADD.S) remain diagnostics rather +than being turned into illegal instructions now that QUOU is implemented. + +The little-endian branch decoder also adds BANY/BNONE/BALL/BNALL, BBC/BBS +and BBCI/BBSI. Immediate bit numbers are not register references during window +checks; register bit indices use their low five bits. Taken-flow tracking retains +correct loop-exit behavior. Other endianness/configurations are not qualified. + +SCOMPARE1 supports RSR/WSR/XSR and retains per-execution-state values. S32C1I +computes the aligned word address, supplies expected/desired values to a single +bus compare-exchange callback and returns the original memory word in its +register operand whether or not the comparison matches. Aliasing the result and +base register uses the old base. Missing/unsupported callbacks fail explicitly; +there is no read/write fallback that pretends to be atomic. Callback errors and +alignment failures leave CPU state unchanged, while window overflow takes +priority before touching memory. + +Callbacks must not re-enter or mutate the currently executing CPU state. Input +and clock changes for that CPU are queued for instruction boundaries; these +execution APIs do not provide concurrent host-thread state access. A future SoC +bus/event bridge must honor that ownership contract. + +The callback must exclude conflicting writes from **all** bus agents. It is +responsible for allowed memory regions and real indivisibility; serialized test +callbacks do not establish host-thread safety. This profile uses strong compare- +exchange and does not inject the implementation-dependent spurious-failure +alternative. Caches, memory types/protection, hardware arbitration, pipeline +ordering, interrupt/RTOS preservation of SCOMPARE1 and full SMP remain unqualified. +MEMW in the current synchronous model does not establish a real cache-coherence +or multicore ordering model. No shared SoC RAM bus or dual-core scheduler is +registered by these tests. + +Native acceptance adds 128 arithmetic/bit-branch assertions and 67 conditional- +store assertions: signed limits, division faults/window priority, bit endpoints, +success/failure comparisons, base/result alias, offset wrap, unsupported regions, +missing callbacks, fault retention and exactly one callback without normal bus +read/write fallback. Existing system/window/interrupt/timer checks still pass. + +The expanded assembler referees now pass 9,728 integer cases and 4,608 flow/bit- +branch cases, plus the existing 960 rejected accesses. Actual ESP32 GCC compiles +exchange, compare-and-swap, increment and signed/unsigned division/remainder C +routines for 832 successful executions (5,248 fetched instructions). Sixty-four +pairs of execution states share test RAM and alternate instruction steps so both +load the same value before conditional store; one must retry. Across the suite, +384 CAS callbacks include 128 comparison mismatches and no ordinary stores. +Unsigned wrap cases are included. This proves functional execution and the +callback contract under that interleaving, not a completed dual-core SoC. + +```sh +python tests/vendor/xtensa_integer/run_atomic_c.py \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld \ + --nm /path/to/xtensa-esp32-elf-nm +``` + ## Implemented: standalone firmware integrity inspection `embsim image-info esp32 app.bin` inspects a plaintext original-ESP32 image diff --git a/tests/unit/test_xtensa_arithmetic.c b/tests/unit/test_xtensa_arithmetic.c new file mode 100644 index 000000000..a08d08fec --- /dev/null +++ b/tests/unit/test_xtensa_arithmetic.c @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/xtensa/integer.h" +#include "mcu/xtensa/windows.h" +static void integer(uint32_t a[16],uint32_t word) +{ + unsigned char code[3];size_t used=99; + code[0]=(unsigned char)(word&255u);code[1]=(unsigned char)((word>>8)&255u);code[2]=(unsigned char)((word>>16)&255u); + CHECK_EQ(esk_xtensa_integer_execute(a,code,3,&used),ES_OK);CHECK_EQ(used,3); +} +static es_status execute(esk_xtensa_windows *w,uint32_t word,unsigned *event) +{ + unsigned char code[3];size_t used;esk_xtensa_fault f; + code[0]=(unsigned char)(word&255u);code[1]=(unsigned char)((word>>8)&255u);code[2]=(unsigned char)((word>>16)&255u); + return esk_xtensa_window_execute(w,NULL,code,3,&used,event,&f); +} +static void integer_edges(void) +{ + uint32_t a[16]={0},before[16];unsigned char zero_div[]={0x40,0x23,0xd2};size_t used; + a[2]=99;a[3]=7;a[4]=0;integer(a,0x832340);CHECK_EQ(a[2],7); + a[2]=99;integer(a,0x932340);CHECK_EQ(a[2],99); + a[4]=0x80000000;integer(a,0xa32340);CHECK_EQ(a[2],7); + a[2]=99;integer(a,0xb32340);CHECK_EQ(a[2],99); + a[3]=UINT32_MAX;a[4]=1;integer(a,0x432340);CHECK_EQ(a[2],UINT32_MAX); + integer(a,0x532340);CHECK_EQ(a[2],1);integer(a,0x632340);CHECK_EQ(a[2],1); + integer(a,0x732340);CHECK_EQ(a[2],UINT32_MAX); + a[3]=UINT32_MAX;a[4]=UINT32_MAX;integer(a,0xa22340);CHECK_EQ(a[2],0xfffffffeu); + integer(a,0xb22340);CHECK_EQ(a[2],0); + a[3]=0x80000000;a[4]=2;integer(a,0xa22340);CHECK_EQ(a[2],1); + integer(a,0xb22340);CHECK_EQ(a[2],UINT32_MAX); + a[3]=0x80000000;a[4]=UINT32_MAX;integer(a,0xd22340);CHECK_EQ(a[2],0x80000000u); + integer(a,0xf22340);CHECK_EQ(a[2],0); + a[3]=0xfffffff9;a[4]=3;integer(a,0xd22340);CHECK_EQ(a[2],0xfffffffeu); + integer(a,0xf22340);CHECK_EQ(a[2],UINT32_MAX); + a[3]=UINT32_MAX;integer(a,0xc22340);CHECK_EQ(a[2],0x55555555u); + integer(a,0xe22340);CHECK_EQ(a[2],0); + a[3]=0x80;integer(a,0x232300);CHECK_EQ(a[2],0xffffff80u); + a[3]=0x400000;integer(a,0x2323f0);CHECK_EQ(a[2],0xffc00000u); + a[3]=0;integer(a,0x40f320);CHECK_EQ(a[2],32);integer(a,0x40e320);CHECK_EQ(a[2],31); + a[3]=UINT32_MAX;integer(a,0x40f320);CHECK_EQ(a[2],0);integer(a,0x40e320);CHECK_EQ(a[2],31); + a[3]=1;integer(a,0x40f320);CHECK_EQ(a[2],31);integer(a,0x40e320);CHECK_EQ(a[2],30); + a[4]=0;memcpy(before,a,sizeof a);CHECK_EQ(esk_xtensa_integer_execute(a,zero_div,3,&used),ES_EINVAL); + CHECK_EQ(used,0);CHECK_EQ(memcmp(a,before,sizeof a),0); +} +static void divide_priority(void) +{ + static const uint32_t vectors[3]={0x40010000,0x40010040,0x40010080}; + static const uint32_t win[6]={0x40020000,0x40020040,0x40020080,0x400200c0,0x40020100,0x40020140}; + esk_xtensa_windows w;unsigned event; + esk_xtensa_windows_init(&w,0x40001234,win);esk_xtensa_exception_configure(&w,vectors); + w.cpu.a[2]=42;w.cpu.a[3]=7;w.cpu.a[4]=0; + CHECK_EQ(execute(&w,0xd22340,&event),ES_OK);CHECK_EQ(event,7);CHECK_EQ(w.exccause,6); + CHECK_EQ(w.epc1,0x40001234u);CHECK_EQ(w.cpu.a[2],42); + esk_xtensa_windows_init(&w,0x40001234,win);esk_xtensa_exception_configure(&w,vectors); + w.start=7;w.cpu.a[12]=7;w.cpu.a[13]=0;w.cpu.a[14]=42; + CHECK_EQ(execute(&w,0xc2ecd0,&event),ES_OK);CHECK_EQ(event,1);CHECK_EQ(w.exccause,0); + CHECK_EQ(w.ar[14],42); /* window overflow precedes divide-by-zero */ +} +static void bit_branches(void) +{ + static const struct { uint32_t code,left,right;unsigned yes; } cases[]={ + {0x048237,0,0,0},{0x040237,0,0,1},{0x044237,0,0,1},{0x04c237,0,0,0}, + {0x048237,8,8,1},{0x044237,1,3,0},{0x04c237,1,3,1}, + {0x045237,0x80000000u,UINT32_MAX,0},{0x04d237,0x80000000u,UINT32_MAX,1}, + {0x0472f7,0,0,1},{0x04f2f7,0x80000000u,0,1},{0x04e207,1,0,1} + }; + unsigned i,event; + for(i=0;ireads++;*value=b->value;return ES_OK; +} +static es_status write_mem(void *ctx,uint32_t address,unsigned bytes,uint32_t value) +{ + bus_state *b=(bus_state *)ctx;(void)address;(void)bytes;b->writes++;b->value=value;return ES_OK; +} +static es_status cas(void *ctx,uint32_t address,uint32_t expected,uint32_t desired,uint32_t *observed) +{ + bus_state *b=(bus_state *)ctx;b->calls++;b->address=address;b->expected=expected;b->desired=desired; + *observed=0xdeadbeef; + if(address!=256) return ES_EUNSUPPORTED; + if(b->fail) return ES_EINVAL; + *observed=b->value;if(b->value==expected) b->value=desired;return ES_OK; +} +static es_status execute(esk_xtensa_windows *w,const esk_xtensa_memory *bus,uint32_t word,unsigned *event,esk_xtensa_fault *f) +{ + unsigned char code[3];size_t used=99;es_status st; + code[0]=(unsigned char)(word&255u);code[1]=(unsigned char)((word>>8)&255u);code[2]=(unsigned char)((word>>16)&255u); + st=esk_xtensa_window_execute(w,bus,code,3,&used,event,f); + CHECK_EQ(used,st==ES_OK && !*event ? 3u : 0u);return st; +} +static void compare_and_alias(void) +{ + bus_state b={0};esk_xtensa_memory bus={&b,read_mem,write_mem,cas}; + esk_xtensa_windows one,two;unsigned event;esk_xtensa_fault f; + esk_xtensa_windows_init(&one,0x40000000,NULL);esk_xtensa_windows_init(&two,0x40001000,NULL); + one.cpu.a[3]=256;one.cpu.a[2]=1;two.cpu.a[3]=256;two.cpu.a[2]=2; + CHECK_EQ(execute(&one,&bus,0x00e322,&event,&f),ES_OK);CHECK_EQ(one.cpu.a[2],0);CHECK_EQ(b.value,1); + CHECK_EQ(execute(&two,&bus,0x00e322,&event,&f),ES_OK);CHECK_EQ(two.cpu.a[2],1);CHECK_EQ(b.value,1); + two.scompare1=1;two.cpu.a[2]=2; + CHECK_EQ(execute(&two,&bus,0x00e322,&event,&f),ES_OK);CHECK_EQ(two.cpu.a[2],1);CHECK_EQ(b.value,2); + CHECK_EQ(b.calls,3);CHECK_EQ(b.reads,0);CHECK_EQ(b.writes,0); + two.scompare1=2;two.cpu.a[3]=256; + CHECK_EQ(execute(&two,&bus,0x00e332,&event,&f),ES_OK);CHECK_EQ(b.value,256);CHECK_EQ(two.cpu.a[3],2); + CHECK_EQ(b.address,256);CHECK_EQ(b.expected,2);CHECK_EQ(b.desired,256); + two.cpu.a[3]=0xfffffd04;two.cpu.a[2]=7;two.scompare1=256; + CHECK_EQ(execute(&two,&bus,0xffe322,&event,&f),ES_OK);CHECK_EQ(b.address,256);CHECK_EQ(b.value,7); + bus.read=NULL;bus.write=NULL;two.cpu.a[3]=256;two.cpu.a[2]=8;two.scompare1=7; + CHECK_EQ(execute(&two,&bus,0x00e322,&event,&f),ES_OK);CHECK_EQ(two.cpu.a[2],7);CHECK_EQ(b.value,8); + CHECK_EQ(b.reads,0);CHECK_EQ(b.writes,0); +} +static void failures_and_window_priority(void) +{ + bus_state b={9,0,0,0,0,0,0,0};esk_xtensa_memory bus={&b,read_mem,write_mem,cas}; + esk_xtensa_windows w,before;unsigned event,calls;esk_xtensa_fault f; + esk_xtensa_windows_init(&w,0x40000000,NULL);w.cpu.a[3]=257;w.cpu.a[2]=5;w.scompare1=9;before=w; + CHECK_EQ(execute(&w,&bus,0x00e322,&event,&f),ES_EINVAL);CHECK_EQ(f.kind,ESK_XT_ALIGN); + CHECK_EQ(b.calls,0);CHECK_EQ(memcmp(&w,&before,sizeof w),0);CHECK_EQ(b.value,9); + w.cpu.a[3]=256;before=w;bus.compare_exchange=NULL; + CHECK_EQ(execute(&w,&bus,0x00e322,&event,&f),ES_EUNSUPPORTED);CHECK_EQ(b.reads,0);CHECK_EQ(b.writes,0); + CHECK_EQ(memcmp(&w,&before,sizeof w),0);bus.compare_exchange=cas;b.fail=1; + CHECK_EQ(execute(&w,&bus,0x00e322,&event,&f),ES_EINVAL);CHECK_EQ(f.kind,ESK_XT_ATOMIC); + CHECK_EQ(memcmp(&w,&before,sizeof w),0);CHECK_EQ(b.value,9);b.fail=0; + w.cpu.a[3]=512;before=w; + CHECK_EQ(execute(&w,&bus,0x00e322,&event,&f),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&w,&before,sizeof w),0); + w.cpu.a[3]=257;w.cpu.a[8]=4;w.start=7;calls=b.calls; + CHECK_EQ(execute(&w,&bus,0x00e382,&event,&f),ES_OK);CHECK_EQ(event,1);CHECK_EQ(b.calls,calls); + CHECK_EQ(w.ar[8],4); /* even misalignment waits until the register window is available */ +} +static void special_register(void) +{ + esk_xtensa_windows w;unsigned event;esk_xtensa_fault f; + esk_xtensa_windows_init(&w,0x40000000,NULL);w.ps|=32u|64u;w.cpu.a[3]=0x12345678; + CHECK_EQ(execute(&w,NULL,0x130c30,&event,&f),ES_OK);CHECK_EQ(event,0);CHECK_EQ(w.scompare1,0x12345678u); + CHECK_EQ(execute(&w,NULL,0x030c20,&event,&f),ES_OK);CHECK_EQ(w.cpu.a[2],0x12345678u); + w.cpu.a[4]=9;CHECK_EQ(execute(&w,NULL,0x610c40,&event,&f),ES_OK);CHECK_EQ(w.scompare1,9);CHECK_EQ(w.cpu.a[4],0x12345678u); +} +TEST_MAIN_BEGIN + (void)t_str_eq;RUN(compare_and_alias);RUN(failures_and_window_priority);RUN(special_register); +TEST_MAIN_END diff --git a/tests/unit/test_xtensa_execute.c b/tests/unit/test_xtensa_execute.c index 84e2df09e..2317ef024 100644 --- a/tests/unit/test_xtensa_execute.c +++ b/tests/unit/test_xtensa_execute.c @@ -42,7 +42,7 @@ static void program(void) 0x4b,0x22, 0x0b,0x33, 0x56,0x43,0xff, 0x45,0,0, 0xc6,0,0, 0x4a,0x44, 0x0d,0xf0, 0x3d,0xf0 }; - memory m = {{0},0,0,0,0}; esk_xtensa_memory bus = {&m,read_mem,write_mem}; + memory m = {{0},0,0,0,0}; esk_xtensa_memory bus = {&m,read_mem,write_mem,NULL}; esk_xtensa_state s = {{0},0}; esk_xtensa_fault f; unsigned i, steps = 0; memcpy(m.data, code, sizeof code); for (i = 0; i < 4; i++) m.data[256+i*4] = (unsigned char)(i+1); @@ -53,7 +53,7 @@ static void program(void) } static void memory_and_faults(void) { - memory m = {{0},0,0,0,0}; esk_xtensa_memory bus = {&m,read_mem,write_mem}; + memory m = {{0},0,0,0,0}; esk_xtensa_memory bus = {&m,read_mem,write_mem,NULL}; esk_xtensa_state s = {{0},0}, before; esk_xtensa_fault f; s.a[2]=UINT32_C(0x89abcdef); s.a[3]=256; CHECK_EQ(execute(&s,&bus,0x006322,3,&f),ES_OK); /* s32i a2,a3,0 */ diff --git a/tests/unit/test_xtensa_interrupts.c b/tests/unit/test_xtensa_interrupts.c index e82bbcaa3..a0faf98cc 100644 --- a/tests/unit/test_xtensa_interrupts.c +++ b/tests/unit/test_xtensa_interrupts.c @@ -138,7 +138,7 @@ static void fetched_handler(void) /* Save A2 in EXCSAVE3, acknowledge software input 29, restore A2, RFI 3. */ static const unsigned char handler[]={0x20,0xd3,0x13,0x22,0xa0,1,0x30,0x22,1,0x20,0xe3,0x13,0x20,0xd3,3,0x10,0x33,0}; static const uint32_t h[6]={0x1080,0x10c0,0x1100,0x1140,0x1180,0x11c0}; - unsigned char data[512]={0};esk_xtensa_memory bus={data,code_read,NULL};esk_xtensa_windows w; + unsigned char data[512]={0};esk_xtensa_memory bus={data,code_read,NULL,NULL};esk_xtensa_windows w; esk_xtensa_fault f;unsigned event,steps=0,entries=0; memcpy(data,program,sizeof program);memcpy(data+192,handler,sizeof handler); init(&w);w.cpu.pc=0x1000;CHECK_EQ(esk_xtensa_irq_configure(&w,h),ES_OK); diff --git a/tests/unit/test_xtensa_system.c b/tests/unit/test_xtensa_system.c index f58f5d76c..ad721740f 100644 --- a/tests/unit/test_xtensa_system.c +++ b/tests/unit/test_xtensa_system.c @@ -68,8 +68,8 @@ static void nested_exceptions(void) CHECK_EQ(event,9);CHECK_EQ(w.epc1,epc);CHECK_EQ(w.exccause,9);CHECK_EQ(w.excvaddr,0xdeadbeefu); } before=w;CHECK_EQ(esk_xtensa_raise_exception(&w,64,0,0,&event),ES_EINVAL);CHECK_EQ(memcmp(&w,&before,sizeof w),0); - before=w;CHECK_EQ(execute(&w,0xc22340,&event),ES_EUNSUPPORTED);CHECK_EQ(event,0); - CHECK_EQ(memcmp(&w,&before,sizeof w),0); /* valid, unimplemented QUOU is not an illegal opcode */ + before=w;CHECK_EQ(execute(&w,0x0a2340,&event),ES_EUNSUPPORTED);CHECK_EQ(event,0); + CHECK_EQ(memcmp(&w,&before,sizeof w),0); /* valid, unimplemented ADD.S is not an illegal opcode */ } static void privilege_and_sync(void) { @@ -96,7 +96,7 @@ static void operand_windows_and_illegal(void) esk_xtensa_windows_init(&w,0x40001234,NULL);esk_xtensa_exception_configure(&w,vectors); CHECK_EQ(execute(&w,0,&event),ES_OK);CHECK_EQ(event,7);CHECK_EQ(w.exccause,0);CHECK_EQ(w.epc1,0x40001234u); CHECK_EQ(execute(&w,0xf06d,&event),ES_OK);CHECK_EQ(event,9);CHECK_EQ(w.exccause,0);CHECK_EQ(w.depc,vectors[0]); - before=w;CHECK_EQ(execute(&w,0xc22340,&event),ES_EUNSUPPORTED);CHECK_EQ(event,0); + before=w;CHECK_EQ(execute(&w,0x0a2340,&event),ES_EUNSUPPORTED);CHECK_EQ(event,0); CHECK_EQ(memcmp(&w,&before,sizeof w),0); } static es_status code_read(void *ctx,uint32_t address,unsigned bytes,uint32_t *value) @@ -115,7 +115,7 @@ static void fetched_syscall_program(void) static const unsigned char program[]={0x0c,0x72,0,0x50,0,0x1b,0x22,0x3d,0xf0}; static const unsigned char handler[]={0x30,0xb1,3,0x3b,0x33,0x30,0xb1,0x13,0,0x30,0}; static const uint32_t evectors[3]={0x1040,0x1080,0x10c0}; - unsigned char data[256]={0};esk_xtensa_windows w;esk_xtensa_memory bus={data,code_read,NULL}; + unsigned char data[256]={0};esk_xtensa_windows w;esk_xtensa_memory bus={data,code_read,NULL,NULL}; esk_xtensa_fault fault;unsigned event,steps=0,entries=0; memcpy(data,program,sizeof program);memcpy(data+64,handler,sizeof handler); esk_xtensa_windows_init(&w,0x1000,NULL);esk_xtensa_exception_configure(&w,evectors); diff --git a/tests/unit/test_xtensa_windows.c b/tests/unit/test_xtensa_windows.c index 27bd4fc9d..6236ad087 100644 --- a/tests/unit/test_xtensa_windows.c +++ b/tests/unit/test_xtensa_windows.c @@ -132,7 +132,7 @@ static es_status mem_write(void *ctx,uint32_t address,unsigned bytes,uint32_t va static void exception_memory(void) { esk_xtensa_windows w,before; unsigned event; - test_memory m={0x89abcdef,0,0,0}; esk_xtensa_memory bus={&m,mem_read,mem_write}; + test_memory m={0x89abcdef,0,0,0}; esk_xtensa_memory bus={&m,mem_read,mem_write,NULL}; esk_xtensa_windows_init(&w,0x40000000,vectors); w.cpu.a[3]=192; before=w; CHECK_EQ(execute_bus(&w,&bus,0x090320,3,&event),ES_EUNSUPPORTED); /* L32E only in EXCM */ CHECK_EQ(m.reads,0); CHECK_EQ(memcmp(&w,&before,sizeof w),0); diff --git a/tests/vendor/README.md b/tests/vendor/README.md index 86ca0d278..97d0eaba6 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -24,14 +24,15 @@ toolchains. ## Experimental Xtensa integer kernel `xtensa_integer/run.py` uses a separate GNU Xtensa assembler and objcopy, plus -Python arithmetic, to check 5,376 wide integer instructions against the original +Python arithmetic, to check 9,728 wide integer instructions against the original C kernel. Run with `--assembler PATH --objcopy PATH` on Linux with a host C -compiler. No toolchain is vendored or linked. LX106 binutils supports this -shared integer subset; it does not qualify the full ESP32 LX6 configuration. +compiler. The expanded runner requires an actual ESP32-configured assembler +for its ISA options. No toolchain is vendored or linked, and these checks do +not qualify the complete ESP32 LX6 configuration. See [scope and reproduction](../../docs/engineering/esp32-dual-core.md). `xtensa_integer/run_execute.py` additionally uses a separate linker, with -relaxation disabled, for 3,840 flow/memory/density cases and 960 rejected bus +relaxation disabled, for 4,608 flow/memory/density cases and 960 rejected bus accesses. Supply `--assembler`, `--objcopy` and `--linker` paths. It compares all registers, PC and exact bus transactions, including failed-access retention. It does not establish architectural exception delivery or complete ISA fidelity. @@ -69,3 +70,9 @@ comparisons against 263,719 individual oracle ticks. Both take `--core-isa PATH` and compile the original C modules with the host compiler; the vendor header is external reference data, not linked into the app. These checks do not qualify SoC routing, instruction timing, NMI sampling or interrupt-service latency. + +`xtensa_integer/run_atomic_c.py` compiles real ESP32 exchange/CAS/increment and +division functions: 832 executions, including 64 pairs of instruction-interleaved +execution states forcing a CAS retry. Supply `--compiler`, `--objcopy`, `--linker` +and `--nm`. Its callback is a serialized fixture, not a host-thread-safe shared +SoC RAM model or proof of full SMP/coherence. diff --git a/tests/vendor/xtensa_integer/run.py b/tests/vendor/xtensa_integer/run.py index 22f51e594..91314756e 100644 --- a/tests/vendor/xtensa_integer/run.py +++ b/tests/vendor/xtensa_integer/run.py @@ -3,8 +3,9 @@ # Copyright 2026 Junior Deogracias """Cross-check the experimental integer kernel with separately assembled bytes. -GNU binutils is an external encoding referee, never linked or copied. LX106 -binutils can assemble this shared integer subset; it is not an LX6 system referee. +GNU binutils is an external encoding referee, never linked or copied. The +expanded subset requires an ESP32-configured assembler for its ISA options. +This is not a complete LX6 system referee. Requires a host C compiler supporting -shared/-fPIC and Python ctypes (Linux). """ import argparse @@ -27,7 +28,7 @@ def main(): rng = random.Random(0x32) cases = [] operations = ['add', 'sub', 'and', 'or', 'xor', 'addx2', 'addx4', 'addx8', - 'subx2', 'subx4', 'subx8', 'mull', 'neg', 'abs', 'movi', 'addi', 'addmi', 'slli', 'srli', 'srai', 'extui'] + 'subx2', 'subx4', 'subx8', 'mull', 'neg', 'abs', 'movi', 'addi', 'addmi', 'slli', 'srli', 'srai', 'extui', 'moveqz','movnez','movltz','movgez','min','max','minu','maxu','muluh','mulsh','quou','quos','remu','rems','sext','nsa','nsau'] for operation in operations: for k in range(256): regs = [rng.getrandbits(32) for _ in range(16)] @@ -35,6 +36,9 @@ def main(): if k < 5: regs[s] = [0, 1, 0x7fffffff, 0x80000000, 0xffffffff][k] regs[t] = regs[s] + if operation in ['quou','quos','remu','rems']: + if k==5:s,t=2,3;regs[s]=0x80000000;regs[t]=0xffffffff + if regs[t]==0:regs[t]=1 x, y = regs[s], regs[t] instruction = f'{operation} a{d},a{s},a{t}' if operation == 'add': result = x+y @@ -45,6 +49,32 @@ def main(): elif operation.startswith('addx'): result = x*int(operation[-1])+y elif operation.startswith('subx'): result = x*int(operation[-1])-y elif operation == 'mull': result = x*y + elif operation in ['moveqz','movnez','movltz','movgez']: + yes = y==0 if operation=='moveqz' else y!=0 if operation=='movnez' else y>=2**31 if operation=='movltz' else y<2**31 + result=x if yes else regs[d] + elif operation in ['min','max','minu','maxu']: + left=x if operation.endswith('u') or x<2**31 else x-2**32 + right=y if operation.endswith('u') or y<2**31 else y-2**32 + result=min(left,right) if operation.startswith('min') else max(left,right) + elif operation in ['muluh','mulsh']: + left=x if operation=='muluh' or x<2**31 else x-2**32 + right=y if operation=='muluh' or y<2**31 else y-2**32 + result=(left*right)>>32 + elif operation in ['quou','quos','remu','rems']: + if operation in ['quou','remu']:result=x//y if operation=='quou' else x%y + else: + left=x if x<2**31 else x-2**32;right=y if y<2**31 else y-2**32 + quotient=abs(left)//abs(right) + if (left<0)!=(right<0):quotient=-quotient + result=quotient if operation=='quos' else left-quotient*right + elif operation == 'sext': + bit=rng.randrange(7,23);instruction=f'sext a{d},a{s},{bit}' + result=x&((1<<(bit+1))-1) + if result&(1<=2**31 else x + result=32-value.bit_length()-(operation=='nsa') elif operation == 'neg': instruction = f'neg a{d},a{s}'; result = -x elif operation == 'abs': diff --git a/tests/vendor/xtensa_integer/run_atomic_c.py b/tests/vendor/xtensa_integer/run_atomic_c.py new file mode 100644 index 000000000..0671eb2ae --- /dev/null +++ b/tests/vendor/xtensa_integer/run_atomic_c.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""Real ESP32 compiler atomics/division with one shared indivisible callback. + +Two execution states are interleaved at instruction boundaries to force CAS +retry. This is not a dual-core SoC, host-thread or memory-coherence qualification. +""" +import argparse +import ctypes as C +from pathlib import Path +import random +import subprocess +import tempfile +from run_execute import Fault, Read, Write, CAS, Memory, ROOT, MASK +from run_windows_c import Windows + +SOURCE=''' +unsigned exchange(volatile unsigned *p,unsigned value) { return __atomic_exchange_n(p,value,__ATOMIC_SEQ_CST); } +unsigned increment(volatile unsigned *p) { return __sync_add_and_fetch(p,1); } +unsigned cas_bool(volatile unsigned *p,unsigned expected,unsigned desired) { return __sync_bool_compare_and_swap(p,expected,desired); } +unsigned divide_u(unsigned x,unsigned y) { return x/y; } +int divide_s(int x,int y) { return x/y; } +unsigned remainder_u(unsigned x,unsigned y) { return x%y; } +int remainder_s(int x,int y) { return x%y; } +''' + + +def main(): + ap=argparse.ArgumentParser(description=__doc__) + for name in ['compiler','objcopy','linker','nm']:ap.add_argument('--'+name,required=True) + ap.add_argument('--cc',default='cc');args=ap.parse_args();rng=random.Random(0xa70) + with tempfile.TemporaryDirectory(prefix='embsim-atomic-c-') as tmp: + out=Path(tmp);src=out/'atomic.c';obj=out/'atomic.o';elf=out/'atomic.elf';binary=out/'atomic.bin' + src.write_text(SOURCE,encoding='utf-8') + subprocess.run([args.compiler,'-B'+str(Path(args.compiler).parent)+'/','-O1','-ffreestanding','-fno-builtin','-c',str(src),'-o',str(obj)],check=True) + subprocess.run([args.linker,'--no-relax','-Ttext=0x1000','--entry=exchange',str(obj),'-o',str(elf)],check=True) + subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(elf),str(binary)],check=True) + symbols={} + for line in subprocess.check_output([args.nm,str(elf)],text=True).splitlines(): + f=line.split() + if len(f)==3:symbols[f[2]]=int(f[0],16) + lib=out/'atomic.so' + subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC', + '-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'), + *[str(ROOT/'core/mcu/xtensa'/f) for f in ['integer.c','execute.c','windows.c','interrupts.c']],'-o',str(lib)],check=True) + api=C.CDLL(str(lib));init=api.esk_xtensa_windows_init + init.argtypes=[C.POINTER(Windows),C.c_uint32,C.POINTER(C.c_uint32)];init.restype=None + step=api.esk_xtensa_window_step;step.argtypes=[C.POINTER(Windows),C.POINTER(Memory),C.POINTER(C.c_uint),C.POINTER(Fault)];step.restype=C.c_int + code=binary.read_bytes();shared={'value':0,'calls':0,'failed':0,'writes':0} + @Read + def read(ctx,address,size,value): + if 0x1000<=address and address+size<=0x1000+len(code):value[0]=int.from_bytes(code[address-0x1000:address-0x1000+size],'little');return 0 + if address==0x200000 and size==4:value[0]=shared['value'];return 0 + return -1 + @Write + def write(ctx,address,size,value):shared['writes']+=1;return -1 + @CAS + def cas(ctx,address,expected,desired,observed): + if address!=0x200000:return -1 + shared['calls']+=1;observed[0]=shared['value'] + if shared['value']==expected:shared['value']=desired + else:shared['failed']+=1 + return 0 + bus=Memory(None,read,write,cas);stop=0x3ffffff0;instructions=0;executions=0;pairs=0 + def state(name,args,index=0): + w=Windows();init(C.byref(w),symbols[name],None);w.ps|=1<<16;w.cpu.a[4]=(1<<30)|stop + w.cpu.a[0]=0x12340000+index;w.cpu.a[1]=0x201000+index*1024 + for i,value in enumerate(args):w.cpu.a[6+i]=value&MASK + return w + def tick(w): + nonlocal instructions + event=C.c_uint();fault=Fault();status=step(C.byref(w),C.byref(bus),C.byref(event),C.byref(fault)) + assert status==0 and event.value==0,(hex(fault.pc),hex(fault.opcode),fault.kind,event.value) + instructions+=1 + def finish(w,index=0): + assert w.cpu.pc==stop and w.base==0 and w.start==1 + assert w.cpu.a[0]==0x12340000+index and w.cpu.a[1]==0x201000+index*1024 + def run(name,args): + nonlocal executions + w=state(name,args) + for _ in range(200): + if w.cpu.pc==stop:break + tick(w) + finish(w);executions+=1;return w.cpu.a[6] + for trial in range(64): + initial=[0,MASK,MASK-1,0x80000000][trial] if trial<4 else rng.getrandbits(32);desired=initial if trial==0 else rng.getrandbits(32) + shared['value']=initial;assert run('exchange',[0x200000,desired])==initial and shared['value']==desired + for match in [False,True]: + shared['value']=initial;expected=initial if match else initial^1 + assert run('cas_bool',[0x200000,expected,desired])==match + assert shared['value']==(desired if match else initial) + # Both CPUs load the same value before the first conditional store. + shared['value']=initial;failed=shared['failed'];states=[state('increment',[0x200000],i) for i in range(2)] + for _ in range(200): + for w in states: + if w.cpu.pc!=stop:tick(w) + if all(w.cpu.pc==stop for w in states):break + for i,w in enumerate(states):finish(w,i) + assert shared['value']==(initial+2)&MASK + assert sorted(w.cpu.a[6] for w in states)==sorted([(initial+1)&MASK,(initial+2)&MASK]) + assert shared['failed']>failed,'CAS retry was not exercised' + executions+=2;pairs+=1 + for name in ['divide_u','divide_s','remainder_u','remainder_s']: + for trial in range(128): + edges=[(0,1),(MASK,1),(0x80000000,1),(0x7fffffff,MASK),(MASK-6,3),(7,MASK-2),(0x80000000,2),(0x80000000,MASK)] + x,y=edges[trial] if trial= signed(y), 'bltu': x < y, 'bgeu': x >= y}[operation] if taken: next_pc = target + elif operation in ['bany','bnone','ball','bnall','bbc','bbs','bbci','bbsi']: + left,right=regs[s],regs[t] + if operation in ['bbci','bbsi']: + bit=k%32;asm+=f' a{s},{bit},.L{i}{target-pc:+d}' + taken=((left>>bit)&1)==(operation=='bbsi') + else: + asm+=f' a{s},a{t},.L{i}{target-pc:+d}' + if operation in ['bany','bnone']:taken=((left&right)!=0)==(operation=='bany') + elif operation in ['ball','bnall']:taken=((left&right)==right)==(operation=='ball') + else:taken=((left>>(right&31))&1)==(operation=='bbs') + if taken:next_pc=target elif operation in ['beqi','bnei','blti','bgei','bltui','bgeui']: unsigned = operation in ['bltui','bgeui'] values = ([32768,65536] if unsigned else [-1,1])+[2,3,4,5,6,7,8,10,12,16,32,64,128,256] diff --git a/tests/vendor/xtensa_integer/run_windows_c.py b/tests/vendor/xtensa_integer/run_windows_c.py index 064c927c6..8b99be824 100644 --- a/tests/vendor/xtensa_integer/run_windows_c.py +++ b/tests/vendor/xtensa_integer/run_windows_c.py @@ -22,7 +22,7 @@ class Windows(C.Structure): _fields_=[('cpu',State),('ar',C.c_uint32*64),('ps',C.c_uint32),('base',C.c_uint32), ('start',C.c_uint32),('epc1',C.c_uint32),('lbeg',C.c_uint32),('lend',C.c_uint32), ('lcount',C.c_uint32),('sar',C.c_uint32),('exccause',C.c_uint32),('excvaddr',C.c_uint32), - ('depc',C.c_uint32),('exceptions_enabled',C.c_uint32),('exception_vectors',C.c_uint32*3),('irq',Interrupts),('vectors',C.c_uint32*6)] + ('depc',C.c_uint32),('scompare1',C.c_uint32),('exceptions_enabled',C.c_uint32),('exception_vectors',C.c_uint32*3),('irq',Interrupts),('vectors',C.c_uint32*6)] NESTED=''' From 8417860528a53fd961aaeb0821f0c18d3f355c92 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 01:38:49 +0000 Subject: [PATCH 11/57] Add ESP32 internal-memory fabric and serialized two-core execution harness --- CHANGELOG.md | 15 +++ core/mcu/esp32/fabric.c | 112 ++++++++++++++++ core/mcu/esp32/fabric.h | 24 ++++ core/mcu/esp32/machine.c | 62 +++++++++ core/mcu/esp32/machine.h | 29 +++++ core/mcu/xtensa/execute.c | 3 +- core/mcu/xtensa/execute.h | 4 +- core/mcu/xtensa/windows.c | 3 +- docs/STATUS.md | 9 +- docs/engineering/esp32-dual-core.md | 73 ++++++++++- tests/unit/test_esp32_fabric.c | 110 ++++++++++++++++ tests/unit/test_esp32_machine.c | 88 +++++++++++++ tests/unit/test_xtensa_atomic.c | 4 +- tests/unit/test_xtensa_execute.c | 4 +- tests/unit/test_xtensa_interrupts.c | 2 +- tests/unit/test_xtensa_system.c | 2 +- tests/unit/test_xtensa_windows.c | 2 +- tests/vendor/README.md | 8 ++ tests/vendor/xtensa_integer/run_execute.py | 2 +- tests/vendor/xtensa_integer/run_machine_c.py | 129 +++++++++++++++++++ 20 files changed, 672 insertions(+), 13 deletions(-) create mode 100644 core/mcu/esp32/fabric.c create mode 100644 core/mcu/esp32/fabric.h create mode 100644 core/mcu/esp32/machine.c create mode 100644 core/mcu/esp32/machine.h create mode 100644 tests/unit/test_esp32_fabric.c create mode 100644 tests/unit/test_esp32_machine.c create mode 100644 tests/vendor/xtensa_integer/run_machine_c.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cc6e571b..c434d484a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental ESP32 internal memory and two-context harness + +- Added native SRAM0/1/2 and RTC memory views, including SRAM1's reversed + instruction-word ordering, CPU0-only RTC fast access and per-core boot-remap + views. Optional ROM is externally supplied; cache, flash, peripheral and + unmapped accesses remain unsupported. Host image loads preflight all writes. +- Separated instruction fetch from data access, retaining existing flat-bus + fallback. Serialized SRAM1/2 compare-exchange now backs two independent CPU + contexts with shared RAM, explicit pause/resume, fault isolation and private + interrupt/clock state. No guessed clock ticks or silicon reset defaults. +- Added exhaustive SRAM1 alias/boundary/failure fixtures and native-bus acceptance + with actual ESP32 compiled tasks: 256 executions, 64 deterministic schedule + replays and 65,590 instructions. This is not a runnable board target, ROM boot, + DPORT/cache/coherence implementation, host-thread safety or complete SMP. + ### Experimental Xtensa integer and conditional-store extension - Added conditional moves, signed/unsigned min/max, high products, division and diff --git a/core/mcu/esp32/fabric.c b/core/mcu/esp32/fabric.c new file mode 100644 index 000000000..1eeaa58a2 --- /dev/null +++ b/core/mcu/esp32/fabric.c @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* Address facts: ESP32 TRM v5.9, section 3.3.2. No vendor code copied. */ +#include "mcu/esp32/fabric.h" +#include +struct view { struct esk_esp32_fabric *f;unsigned cpu; }; +struct esk_esp32_fabric { + esk_alloc *a; + unsigned char sram0[0x20000],sram1[0x20000],sram2[0x32000],fast[0x2000],slow[0x2000]; + unsigned char *rom[2]; + unsigned remap[2];struct view view[2]; +}; +struct location { unsigned char *byte;int readonly,word_only,executable,atomic; }; +static int within(uint32_t addr,uint32_t base,uint32_t size) { return addr>=base && addr-basef;uint32_t offset; + memset(p,0,sizeof *p); + if(within(addr,0x3ffae000,0x32000)) { p->byte=f->sram2+(addr-0x3ffae000);p->atomic=1; } + else if(within(addr,0x3ffe0000,0x20000)) { p->byte=f->sram1+(addr-0x3ffe0000);p->atomic=1; } + else if(within(addr,0x40080000,0x20000)) { p->byte=f->sram0+(addr-0x40080000);p->word_only=p->executable=1; } + else if(within(addr,0x400a0000,0x20000)) { + if(f->remap[v->cpu] && within(addr,0x400b0000,0x8000)) return ES_EUNSUPPORTED; + offset=addr-0x400a0000;p->byte=f->sram1+(0x1fffcu-(offset&~3u))+(offset&3u); + p->word_only=p->executable=1; + } else if(within(addr,0x40000000,0x60000)) { + if(f->remap[v->cpu] && addr<0x40008000) { + offset=0x10000u+(addr-0x40000000);p->byte=f->sram1+(0x1fffcu-(offset&~3u))+(offset&3u); + } else { if(!f->rom[0]) return ES_EUNSUPPORTED;p->byte=f->rom[0]+(addr-0x40000000);p->readonly=1; } + p->word_only=p->executable=1; + } else if(within(addr,0x3ff90000,0x10000)) { + if(!f->rom[1]) return ES_EUNSUPPORTED; + p->byte=f->rom[1]+(addr-0x3ff90000);p->readonly=1; + } else if(within(addr,0x3ff80000,0x2000)||within(addr,0x400c0000,0x2000)) { + if(v->cpu) return ES_EUNSUPPORTED; + offset=addr<0x40000000 ? addr-0x3ff80000 : addr-0x400c0000; + p->byte=f->fast+offset;p->word_only=addr>=0x40000000;p->executable=p->word_only; + } else if(within(addr,0x50000000,0x2000)) { p->byte=f->slow+(addr-0x50000000);p->executable=1; } + else return ES_EUNSUPPORTED; + return ES_OK; +} +static es_status access(struct view *v,uint32_t address,unsigned bytes,int fetch,int write,uint32_t *value) +{ + struct location at[4];unsigned i;uint32_t result=0; + if(!v || !value || (bytes<1 || bytes>4 || (!fetch && bytes==3)) || (uint64_t)address+bytes>UINT64_C(0x100000000)) return ES_EINVAL; + if(!fetch && (address&(bytes-1u))) return ES_EINVAL; + for(i=0;i>(8*i))&255u); + else { for(i=0;ia=a;for(i=0;i<2;i++) { f->view[i].f=f;f->view[i].cpu=i; }*out=f;return ES_OK; +} +void esk_esp32_fabric_destroy(esk_esp32_fabric *f) +{ + if(!f) return; + if(f->rom[0]) esk_free(f->a,f->rom[0],0x60000); + if(f->rom[1]) esk_free(f->a,f->rom[1],0x10000); + esk_free(f->a,f,sizeof *f); +} +es_status esk_esp32_fabric_rom(esk_esp32_fabric *f,unsigned which,const unsigned char *data,size_t len) +{ + unsigned char *copy;size_t expected=which ? 0x10000u : 0x60000u; + if(!f || which>1 || !data || len!=expected) return ES_EINVAL; + copy=(unsigned char *)esk_malloc(f->a,len);if(!copy) return ES_ENOMEM; + memcpy(copy,data,len);if(f->rom[which]) esk_free(f->a,f->rom[which],len);f->rom[which]=copy;return ES_OK; +} +es_status esk_esp32_fabric_remap(esk_esp32_fabric *f,unsigned cpu,int enabled) +{ + if(!f || cpu>1) return ES_EINVAL; + f->remap[cpu]=enabled ? 1u : 0u;return ES_OK; +} +es_status esk_esp32_fabric_load(esk_esp32_fabric *f,unsigned cpu,uint32_t addr,const unsigned char *data,size_t len) +{ + size_t i;struct location at;es_status st; + if(!f || cpu>1 || (!data && len) || len>UINT64_C(0x100000000)-(uint64_t)addr) return ES_EINVAL; + for(i=0;iview[cpu],addr+(uint32_t)i,&at);if(st!=ES_OK) return st;if(at.readonly) return ES_EINVAL; } + for(i=0;iview[cpu],addr+(uint32_t)i,&at);*at.byte=data[i]; } + return ES_OK; +} +esk_xtensa_memory esk_esp32_fabric_bus(esk_esp32_fabric *f,unsigned cpu) +{ + esk_xtensa_memory bus={NULL,NULL,NULL,NULL,NULL}; + if(f && cpu<2) { bus.ctx=&f->view[cpu];bus.read=read_bus;bus.write=write_bus;bus.compare_exchange=cas_bus;bus.fetch=fetch_bus; } + return bus; +} diff --git a/core/mcu/esp32/fabric.h b/core/mcu/esp32/fabric.h new file mode 100644 index 000000000..9f720a330 --- /dev/null +++ b/core/mcu/esp32/fabric.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_ESP32_FABRIC_H +#define ESK_ESP32_FABRIC_H +#include "mcu/xtensa/execute.h" +#include "util/alloc.h" +typedef struct esk_esp32_fabric esk_esp32_fabric; +/* Experimental static internal-memory profile, not the full SoC bus. SRAM0 + * non-cache 128K, SRAM1 128K (reversed instruction words), SRAM2 200K, RTC + * fast/slow 8K each. RTC fast is PRO-only. No cache/MMU/MPU, flash, peripherals, + * DMA, RTC power/retention or physical reset qualification. RAM starts zero only + * for deterministic setup. Serialized single-thread calls only; no re-entry. */ +es_status esk_esp32_fabric_create(esk_alloc *a,esk_esp32_fabric **out); +void esk_esp32_fabric_destroy(esk_esp32_fabric *f); +/* Optional externally provided ROM0/ROM1, exact 384K/64K. No ROM is fabricated. */ +es_status esk_esp32_fabric_rom(esk_esp32_fabric *f,unsigned which,const unsigned char *data,size_t len); +/* Host-controlled boot-remap bit, not a DPORT register model. */ +es_status esk_esp32_fabric_remap(esk_esp32_fabric *f,unsigned cpu,int enabled); +/* All-or-nothing RAM image write through the selected core's address view. */ +es_status esk_esp32_fabric_load(esk_esp32_fabric *f,unsigned cpu,uint32_t address,const unsigned char *data,size_t len); +/* Fetch and data accesses differ: instruction-only views permit data words, + * not narrow data operations. CAS is limited to SRAM1/2 data views. */ +esk_xtensa_memory esk_esp32_fabric_bus(esk_esp32_fabric *f,unsigned cpu); +#endif diff --git a/core/mcu/esp32/machine.c b/core/mcu/esp32/machine.c new file mode 100644 index 000000000..05e7c36f5 --- /dev/null +++ b/core/mcu/esp32/machine.c @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "mcu/esp32/machine.h" +#include +struct esk_esp32_machine { + esk_alloc *a;esk_esp32_fabric *fabric; + esk_xtensa_windows cpu[2];uint64_t retired[2];unsigned running[2]; +}; +es_status esk_esp32_machine_create(esk_alloc *a,esk_esp32_machine **out) +{ + esk_esp32_machine *m;es_status st;unsigned i; + if(!out) return ES_EINVAL; + *out=NULL;if(!a) return ES_EINVAL; + m=(esk_esp32_machine *)esk_calloc(a,1,sizeof *m);if(!m) return ES_ENOMEM; + m->a=a;st=esk_esp32_fabric_create(a,&m->fabric); + if(st!=ES_OK) { esk_free(a,m,sizeof *m);return st; } + for(i=0;i<2;i++) esk_xtensa_windows_init(&m->cpu[i],0,NULL); + *out=m;return ES_OK; +} +void esk_esp32_machine_destroy(esk_esp32_machine *m) +{ + if(!m) return; + esk_esp32_fabric_destroy(m->fabric);esk_free(m->a,m,sizeof *m); +} +esk_esp32_fabric *esk_esp32_machine_fabric(esk_esp32_machine *m) { return m ? m->fabric : NULL; } +es_status esk_esp32_machine_configure(esk_esp32_machine *m,unsigned cpu,uint32_t entry) +{ + if(!m || cpu>1) return ES_EINVAL; + esk_xtensa_windows_init(&m->cpu[cpu],entry,NULL);m->running[cpu]=0;m->retired[cpu]=0;return ES_OK; +} +esk_xtensa_windows *esk_esp32_machine_core(esk_esp32_machine *m,unsigned cpu) { return m && cpu<2 ? &m->cpu[cpu] : NULL; } +es_status esk_esp32_machine_run_core(esk_esp32_machine *m,unsigned cpu,int run) +{ + if(!m || cpu>1) return ES_EINVAL; + m->running[cpu]=run ? 1u : 0u;return ES_OK; +} +es_status esk_esp32_machine_step(esk_esp32_machine *m,unsigned cpu,unsigned *event,esk_xtensa_fault *fault) +{ + esk_xtensa_memory bus;es_status st; + if(event) *event=0; + if(fault) memset(fault,0,sizeof *fault); + if(!m || cpu>1 || !event) return ES_EINVAL; + if(!m->running[cpu]) { *event=19;return ES_OK; } + bus=esk_esp32_fabric_bus(m->fabric,cpu); + st=esk_xtensa_window_step(&m->cpu[cpu],&bus,event,fault); + if(st!=ES_OK) m->running[cpu]=0; + else if(!*event) m->retired[cpu]++; + return st; +} +es_status esk_esp32_machine_round(esk_esp32_machine *m,unsigned events[2],unsigned *failed_cpu,esk_xtensa_fault *fault) +{ + unsigned i;es_status st; + if(failed_cpu) *failed_cpu=2; + if(!m || !events || !failed_cpu) return ES_EINVAL; + events[0]=events[1]=0; + for(i=0;i<2;i++) { + st=esk_esp32_machine_step(m,i,&events[i],fault); + if(st!=ES_OK) { *failed_cpu=i;return st; } + } + return ES_OK; +} +uint64_t esk_esp32_machine_retired(const esk_esp32_machine *m,unsigned cpu) { return m && cpu<2 ? m->retired[cpu] : 0; } diff --git a/core/mcu/esp32/machine.h b/core/mcu/esp32/machine.h new file mode 100644 index 000000000..53135f7d0 --- /dev/null +++ b/core/mcu/esp32/machine.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_ESP32_MACHINE_H +#define ESK_ESP32_MACHINE_H +#include "mcu/esp32/fabric.h" +#include "mcu/xtensa/windows.h" +typedef struct esk_esp32_machine esk_esp32_machine; +/* Experimental two-context execution harness over shared internal memory. + * Deterministic serialized instruction turns, not a cycle scheduler or full SoC. + * No DPORT/reset/stall registers, boot ROM, peripherals, interrupt matrix, + * cache arbitration/coherence or board model. Host controls are debug/test + * operations, not emulated hardware control registers. No concurrent callers. */ +es_status esk_esp32_machine_create(esk_alloc *a,esk_esp32_machine **out); +void esk_esp32_machine_destroy(esk_esp32_machine *m); +esk_esp32_fabric *esk_esp32_machine_fabric(esk_esp32_machine *m); +/* Clear one architecture profile, pause it and set explicit entry. Shared + * memory and per-core remap survive. This is not a silicon reset sequence. */ +es_status esk_esp32_machine_configure(esk_esp32_machine *m,unsigned cpu,uint32_t entry); +/* Mutable profile inspection/setup only between execution turns. */ +esk_xtensa_windows *esk_esp32_machine_core(esk_esp32_machine *m,unsigned cpu); +es_status esk_esp32_machine_run_core(esk_esp32_machine *m,unsigned cpu,int run); +/* Paused core: event 19, no fetch. Fault pauses that core at the failing PC. + * Interrupt/window/wait events retain their execution-engine meanings. */ +es_status esk_esp32_machine_step(esk_esp32_machine *m,unsigned cpu,unsigned *event,esk_xtensa_fault *fault); +/* One turn per core, 0 then 1; stop at first fault. Earlier successful turns + * remain committed. failed_cpu=2 when neither failed. No implicit clock ticks. */ +es_status esk_esp32_machine_round(esk_esp32_machine *m,unsigned events[2],unsigned *failed_cpu,esk_xtensa_fault *fault); +uint64_t esk_esp32_machine_retired(const esk_esp32_machine *m,unsigned cpu); +#endif diff --git a/core/mcu/xtensa/execute.c b/core/mcu/xtensa/execute.c index d92e8b5e0..d1178984b 100644 --- a/core/mcu/xtensa/execute.c +++ b/core/mcu/xtensa/execute.c @@ -132,7 +132,8 @@ es_status esk_xtensa_step(esk_xtensa_state *state, const esk_xtensa_memory *memo if (!state) return ES_EINVAL; for (i = 0; i < width; i++) { uint32_t addr = state->pc + i; - if (!memory || !memory->read || memory->read(memory->ctx, addr, 1, &v) != ES_OK) + if (!memory || (!memory->fetch && !memory->read) || + (memory->fetch ? memory->fetch : memory->read)(memory->ctx, addr, 1, &v) != ES_OK) return fail(fault, ESK_XT_FETCH, state->pc, addr, 0); code[i] = (unsigned char)(v & 255u); if (i == 0 && (code[0] & 15u) < 8) width = 3; diff --git a/core/mcu/xtensa/execute.h b/core/mcu/xtensa/execute.h index 6055b8b22..d3429e7b6 100644 --- a/core/mcu/xtensa/execute.h +++ b/core/mcu/xtensa/execute.h @@ -20,6 +20,8 @@ typedef struct esk_xtensa_memory { * Return original memory in observed on success, whether matched or not. * On failure no write may complete. No read/write fallback is allowed. */ es_status (*compare_exchange)(void *ctx,uint32_t address,uint32_t expected,uint32_t desired,uint32_t *observed); + /* Optional instruction-bus access. Null uses read for legacy flat test buses. */ + es_status (*fetch)(void *ctx,uint32_t address,unsigned bytes,uint32_t *value); } esk_xtensa_memory; enum { ESK_XT_OK, ESK_XT_UNSUPPORTED, ESK_XT_TRUNCATED, ESK_XT_FETCH, ESK_XT_ALIGN, ESK_XT_LOAD, ESK_XT_STORE, ESK_XT_DIV_ZERO, ESK_XT_ATOMIC }; @@ -33,7 +35,7 @@ es_status esk_xtensa_execute(esk_xtensa_state *state, const esk_xtensa_memory *m es_status esk_xtensa_execute_flow(esk_xtensa_state *state, const esk_xtensa_memory *memory, const unsigned char *code, size_t available, size_t *consumed, esk_xtensa_fault *fault, unsigned *sequential); -/* Fetch exactly 2/3 bytes through byte reads, then execute one instruction. */ +/* Fetch exactly 2/3 bytes through fetch (or read fallback), then execute. */ es_status esk_xtensa_step(esk_xtensa_state *state, const esk_xtensa_memory *memory, esk_xtensa_fault *fault); #endif diff --git a/core/mcu/xtensa/windows.c b/core/mcu/xtensa/windows.c index 0380ddf62..306a23cb3 100644 --- a/core/mcu/xtensa/windows.c +++ b/core/mcu/xtensa/windows.c @@ -303,7 +303,8 @@ es_status esk_xtensa_window_step(esk_xtensa_windows *w, const esk_xtensa_memory } for (i=0;icpu.pc+i; - if (!memory || !memory->read || memory->read(memory->ctx,address,1,&value)!=ES_OK) + if (!memory || (!memory->fetch && !memory->read) || + (memory->fetch ? memory->fetch : memory->read)(memory->ctx,address,1,&value)!=ES_OK) return reject(fault,ESK_XT_FETCH,w->cpu.pc,address,0); code[i]=(unsigned char)(value&255u); if (i==0 && (code[0]&15u)<8) width=3; diff --git a/docs/STATUS.md b/docs/STATUS.md index 337399a9f..3b89d321e 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -47,7 +47,14 @@ Conditional moves/min/max/high products/division/normalization and bit branches now pass 128 additional native assertions. SCOMPARE1/S32C1I require one indivisible bus callback, with no non-atomic fallback (67 assertions). Another 832 actual ESP32 compiled atomic/division runs pass, including 64 interleaved increment -pairs that force retry. This does not establish a shared SoC bus or full SMP. +pairs that force retry. A native internal-memory provider now adds SRAM0/1/2, +word-reversed SRAM1 instruction aliases, PRO-only RTC fast RAM, shared RTC slow +RAM, optional external ROM and per-core host-controlled boot remap. A serialized +two-context harness shares this RAM while keeping CPU/interrupt/clock state +private. Native fixtures cover address/permission/remap/fault/wait boundaries; +256 actual ESP32 compiled task executions and 64 replay comparisons pass through +the native bus (65,590 instructions). This remains a bounded memory/execution +harness: cache/MMU, DPORT hardware controls, complete SMP and SoC boot are absent. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index c1d6babf5..78ac17839 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -1,6 +1,6 @@ # Original ESP32 dual-core target: identity and implementation gates -**Status: firmware inspection and experimental integer/window/loop execution implemented; no executable target is added.** Requested by +**Status: firmware inspection, experimental Xtensa execution and a two-context internal-RAM harness implemented; no runnable board target is added.** Requested by the owner on 2026-11-04. This is the original ESP32 family, not ESP32-C3, ESP32-S2 or ESP32-S3. The generic marketplace title “ESP-32 / ESP-32S WiFi+Bluetooth Dual Cores” does not identify a reproducible board revision. @@ -444,6 +444,77 @@ python tests/vendor/xtensa_integer/run_atomic_c.py \ --nm /path/to/xtensa-esp32-elf-nm ``` +## Implemented: internal RAM and two-context execution harness + +`core/mcu/esp32/fabric.c` implements a bounded native memory provider from +ESP32 TRM v5.9 section 3.3.2 and ESP-IDF v4.4.7's +[SoC address definitions](https://github.com/espressif/esp-idf/blob/v4.4.7/components/soc/esp32/include/soc/soc.h). +It is not the complete SoC bus. RAM is initialized to zero for deterministic +fixtures, not as a claim about silicon power-on contents. + +| Region | Address range (end exclusive) | Implemented access | +| --- | --- | --- | +| SRAM0, non-cache portion | `40080000–400A0000` | Instruction fetch and aligned data words | +| SRAM1 data | `3FFE0000–40000000` | Data bytes/halfwords/words and CAS | +| SRAM1 instruction | `400A0000–400C0000` | Fetch and aligned data words, reversed word ordering | +| SRAM2 | `3FFAE000–3FFE0000` | Data bytes/halfwords/words and CAS | +| RTC fast | `3FF80000–3FF82000`, `400C0000–400C2000` | CPU0 only; data view and instruction alias | +| RTC slow | `50000000–50002000` | Both cores, data and instruction fetch | +| ROM0 | `40000000–40060000` | Read-only fetch/aligned data words, only with supplied ROM | +| ROM1 | `3FF90000–3FFA0000` | Read-only data, only with supplied ROM | + +SRAM1 instruction words reverse the data view's order without reversing byte +lanes: `3FFE0000` aliases `400BFFFC`, and `3FFFFFFC` aliases `400A0000`. +The instruction engine now uses an optional distinct fetch callback; existing +flat providers retain the read fallback. This lets narrow instruction fetches +work while rejecting narrow data accesses to instruction-only views. + +A host-configured per-core boot-remap view aliases the first 32 KiB of ROM0 to +SRAM1 instruction addresses `400B0000–400B8000`, disabling that original +instruction alias for the selected core while preserving its data view and the +other core's mapping. This is not a DPORT register implementation. Cache SRAM, +flash/MMU windows, peripheral addresses and unmapped holes fail explicitly. +ROM must be provided externally at its exact region size; none is fabricated or +bundled. Host RAM loading preflights the entire range before writing. A failed +ROM allocation preserves the installed ROM. + +`core/mcu/esp32/machine.c` owns two separate window/exception/interrupt/clock +profiles sharing this provider. Explicit debug controls configure an entry, +pause/resume a core and step one instruction boundary at a time. A round executes +CPU0 then CPU1 and stops at the first failure; earlier successful turns remain +committed. A failing core pauses at its fault. Paused cores report event 19; +WAITI retains event 18 and does not block the peer. Only retired instructions +increment each core's counter; exceptions and suspended turns do not. Clock +advancement remains explicit and private to each profile. + +All calls have one serialized owner. Native SRAM1/2 CAS performs its read and +conditional write within one callback, so instruction-interleaved contexts +cannot intervene. This is not host-thread-safe memory, cache/coherence, +arbitration, an instruction-to-cycle model, DPORT reset/release/stall, retention, +DMA or a complete SMP machine. Reconfiguring one profile preserves shared RAM +and remap settings and is explicitly not a silicon reset sequence. No CPU +constructor, board picker entry, boot ROM execution or ESP-IDF boot is added. + +Native fixtures pass 360,531 memory assertions and 84 machine assertions: every +SRAM1 word and byte lane, remap isolation, RTC +restrictions, rejected access widths/holes, all-or-nothing loading, allocation +failure, shared conditional stores, independent state, fault ordering and +one-core WAITI/timer wake while its peer continues. The separate compiled runner +uses no Python memory/CAS callbacks: actual ESP32 GCC output runs two increment +loops against native shared RAM, checking final count, the sum of distinct +returned increments, unsigned wrap, caller/window/stack preservation, retirement +and exact replay equality. It passes 256 task executions and 64 schedule replays +(65,590 instructions). This is functional interleaving evidence, not full SMP +or real-hardware qualification. + +```sh +python tests/vendor/xtensa_integer/run_machine_c.py \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld \ + --nm /path/to/xtensa-esp32-elf-nm +``` + ## Implemented: standalone firmware integrity inspection `embsim image-info esp32 app.bin` inspects a plaintext original-ESP32 image diff --git a/tests/unit/test_esp32_fabric.c b/tests/unit/test_esp32_fabric.c new file mode 100644 index 000000000..906dba63b --- /dev/null +++ b/tests/unit/test_esp32_fabric.c @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/fabric.h" +static void address_views(void) +{ + esk_alloc a;esk_esp32_fabric *f=NULL;esk_xtensa_memory pro,app;uint32_t v=0,i,lane; + unsigned char bytes[8]={1,2,3,4,5,6,7,8}; + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_fabric_create(&a,&f),ES_OK); + pro=esk_esp32_fabric_bus(f,0);app=esk_esp32_fabric_bus(f,1); + /* Every SRAM1 word and byte lane, not just the two endpoint aliases. */ + for(i=0;i<0x20000;i+=4) { + uint32_t expected=UINT32_C(0xa5c30000)^i; + CHECK_EQ(pro.write(pro.ctx,0x3ffe0000+i,4,expected),ES_OK); + CHECK_EQ(app.read(app.ctx,0x400bfffc-i,4,&v),ES_OK);CHECK_EQ(v,expected); + for(lane=0;lane<4;lane++) { + CHECK_EQ(app.fetch(app.ctx,0x400bfffc-i+lane,1,&v),ES_OK); + CHECK_EQ(v,(expected>>(lane*8))&255u); + } + } + CHECK_EQ(esk_esp32_fabric_load(f,0,0x400a0002,bytes,8),ES_OK); + CHECK_EQ(pro.fetch(pro.ctx,0x400a0002,3,&v),ES_OK);CHECK_EQ(v,0x030201); + CHECK_EQ(app.read(app.ctx,0x3ffffffc,4,&v),ES_OK);CHECK_EQ(v>>16,0x0201); + CHECK_EQ(pro.read(pro.ctx,0x40080000,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(pro.write(pro.ctx,0x40080000,2,1),ES_EUNSUPPORTED); + CHECK_EQ(pro.read(pro.ctx,0x3ffae001,4,&v),ES_EINVAL); + CHECK_EQ(pro.fetch(pro.ctx,0x3ffae000,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(pro.fetch(pro.ctx,0x40070000,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(pro.read(pro.ctx,0x3ff44000,4,&v),ES_EUNSUPPORTED); + CHECK_EQ(pro.fetch(pro.ctx,0x400d0000,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(pro.fetch(pro.ctx,0x40000000,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(pro.write(pro.ctx,0x3ff80000,4,0x12345678),ES_OK); + CHECK_EQ(pro.fetch(pro.ctx,0x400c0001,3,&v),ES_OK);CHECK_EQ(v,0x123456); + CHECK_EQ(app.read(app.ctx,0x3ff80000,4,&v),ES_EUNSUPPORTED); + CHECK_EQ(app.fetch(app.ctx,0x400c0000,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(app.write(app.ctx,0x50000000,4,0x78563412),ES_OK); + CHECK_EQ(pro.fetch(pro.ctx,0x50000001,3,&v),ES_OK);CHECK_EQ(v,0x785634); + CHECK_EQ(pro.compare_exchange(pro.ctx,0x50000000,0,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(pro.write(pro.ctx,0x3ffb0000,4,3),ES_OK); + CHECK_EQ(app.compare_exchange(app.ctx,0x3ffb0000,3,7,&v),ES_OK);CHECK_EQ(v,3); + CHECK_EQ(pro.compare_exchange(pro.ctx,0x3ffb0000,3,9,&v),ES_OK);CHECK_EQ(v,7); + CHECK_EQ(pro.read(pro.ctx,0x3ffb0000,4,&v),ES_OK);CHECK_EQ(v,7); + CHECK_EQ(pro.compare_exchange(pro.ctx,0x40080000,0,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(pro.compare_exchange(pro.ctx,0x3ffb0001,0,1,&v),ES_EINVAL); + CHECK_EQ(pro.write(pro.ctx,0x50001ffc,4,0x11223344),ES_OK); + CHECK_EQ(esk_esp32_fabric_load(f,0,0x50001ffc,bytes,8),ES_EUNSUPPORTED); + CHECK_EQ(pro.read(pro.ctx,0x50001ffc,4,&v),ES_OK);CHECK_EQ(v,0x11223344); + CHECK_EQ(esk_esp32_fabric_load(f,0,0xfffffffc,bytes,8),ES_EINVAL); + CHECK_EQ(esk_esp32_fabric_load(f,2,0,bytes,8),ES_EINVAL); + CHECK(esk_esp32_fabric_bus(f,2).read==NULL); + esk_esp32_fabric_destroy(f);CHECK_EQ(a.live_bytes,0); +} +static void rom_and_remap(void) +{ + esk_alloc a;esk_esp32_fabric *f=NULL;esk_xtensa_memory pro,app;uint32_t v=0; + unsigned char *rom=(unsigned char *)malloc(0x60000);unsigned char code[4]={1,2,3,4}; + CHECK(rom!=NULL);if(!rom) return; + memset(rom,0x5a,0x60000);esk_alloc_init(&a,NULL); + CHECK_EQ(esk_esp32_fabric_create(&a,&f),ES_OK); + pro=esk_esp32_fabric_bus(f,0);app=esk_esp32_fabric_bus(f,1); + CHECK_EQ(esk_esp32_fabric_rom(f,0,rom,0x60000),ES_OK); + CHECK_EQ(esk_esp32_fabric_rom(f,1,rom,0x10000),ES_OK); + rom[0]=0;CHECK_EQ(pro.fetch(pro.ctx,0x40000000,4,&v),ES_OK);CHECK_EQ(v,0x5a5a5a5a); + CHECK_EQ(pro.read(pro.ctx,0x3ff90000,1,&v),ES_OK);CHECK_EQ(v,0x5a); + CHECK_EQ(pro.fetch(pro.ctx,0x3ff90000,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(pro.write(pro.ctx,0x40000000,4,0),ES_EINVAL); + CHECK_EQ(esk_esp32_fabric_load(f,0,0x40000000,code,4),ES_EINVAL); + CHECK_EQ(esk_esp32_fabric_rom(f,0,rom,4),ES_EINVAL); + CHECK_EQ(esk_esp32_fabric_load(f,0,0x400b0000,code,4),ES_OK); + CHECK_EQ(esk_esp32_fabric_remap(f,0,1),ES_OK); + CHECK_EQ(pro.fetch(pro.ctx,0x40000000,4,&v),ES_OK);CHECK_EQ(v,0x04030201); + CHECK_EQ(app.fetch(app.ctx,0x40000000,4,&v),ES_OK);CHECK_EQ(v,0x5a5a5a5a); + CHECK_EQ(pro.fetch(pro.ctx,0x400b0000,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(app.fetch(app.ctx,0x400b0000,4,&v),ES_OK);CHECK_EQ(v,0x04030201); + CHECK_EQ(pro.read(pro.ctx,0x3ffefffc,4,&v),ES_OK);CHECK_EQ(v,0x04030201); + CHECK_EQ(pro.write(pro.ctx,0x40000000,4,0x99887766),ES_OK); + CHECK_EQ(app.fetch(app.ctx,0x400b0000,4,&v),ES_OK);CHECK_EQ(v,0x99887766); + CHECK_EQ(pro.fetch(pro.ctx,0x40008000,1,&v),ES_OK);CHECK_EQ(v,0x5a); + CHECK_EQ(esk_esp32_fabric_remap(f,0,0),ES_OK); + CHECK_EQ(pro.fetch(pro.ctx,0x400b0000,4,&v),ES_OK);CHECK_EQ(v,0x99887766); + esk_esp32_fabric_destroy(f);free(rom);CHECK_EQ(a.live_bytes,0); +} +static void *limited_alloc(void *ctx,size_t n) +{ + unsigned *remaining=(unsigned *)ctx; + if(!*remaining) return NULL; + (*remaining)--;return malloc(n); +} +static void *limited_realloc(void *ctx,void *p,size_t n) { (void)ctx;return realloc(p,n); } +static void limited_free(void *ctx,void *p) { (void)ctx;free(p); } +static void allocation_failures(void) +{ + unsigned remaining=0;es_allocator_t hooks={limited_alloc,limited_realloc,limited_free,&remaining}; + esk_alloc a;esk_esp32_fabric *f=NULL;esk_xtensa_memory bus;uint32_t v=0;size_t live; + unsigned char *rom=(unsigned char *)malloc(0x60000); + CHECK(rom!=NULL);if(!rom) return; + memset(rom,0x67,0x60000);esk_alloc_init(&a,&hooks); + CHECK_EQ(esk_esp32_fabric_create(&a,&f),ES_ENOMEM);CHECK(f==NULL);CHECK_EQ(a.live_bytes,0); + remaining=2;CHECK_EQ(esk_esp32_fabric_create(&a,&f),ES_OK); + CHECK_EQ(esk_esp32_fabric_rom(f,0,rom,0x60000),ES_OK);live=a.live_bytes; + memset(rom,0x89,0x60000); + CHECK_EQ(esk_esp32_fabric_rom(f,0,rom,0x60000),ES_ENOMEM);CHECK_EQ(a.live_bytes,live); + bus=esk_esp32_fabric_bus(f,0); + CHECK_EQ(bus.fetch(bus.ctx,0x40000000,4,&v),ES_OK);CHECK_EQ(v,0x67676767); + esk_esp32_fabric_destroy(f);free(rom);CHECK_EQ(a.live_bytes,0); +} +TEST_MAIN_BEGIN + (void)t_str_eq; + RUN(address_views);RUN(rom_and_remap);RUN(allocation_failures); +TEST_MAIN_END diff --git a/tests/unit/test_esp32_machine.c b/tests/unit/test_esp32_machine.c new file mode 100644 index 000000000..57505aa8b --- /dev/null +++ b/tests/unit/test_esp32_machine.c @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/machine.h" +static void shared_execution(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_esp32_fabric *f;esk_xtensa_memory bus; + esk_xtensa_windows *one,*two;unsigned event,events[2],failed;esk_xtensa_fault fault;uint32_t value; + /* S32C1I A2,A3,0 followed by NOP.N. Independently assembled ESP32 opcode. */ + unsigned char code[]={0x22,0xe3,0x00,0x3d,0xf0}; + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK); + f=esk_esp32_machine_fabric(m);bus=esk_esp32_fabric_bus(f,0); + CHECK_EQ(esk_esp32_fabric_load(f,0,0x40080000,code,sizeof code),ES_OK); + CHECK_EQ(esk_esp32_machine_configure(m,0,0x40080000),ES_OK); + CHECK_EQ(esk_esp32_machine_configure(m,1,0x40080000),ES_OK); + one=esk_esp32_machine_core(m,0);two=esk_esp32_machine_core(m,1); + one->cpu.a[3]=two->cpu.a[3]=0x3ffb0000;one->cpu.a[2]=1;two->cpu.a[2]=2; + CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK);CHECK_EQ(event,19); + CHECK_EQ(one->cpu.pc,0x40080000);CHECK_EQ(esk_esp32_machine_retired(m,0),0); + CHECK_EQ(esk_esp32_machine_run_core(m,0,1),ES_OK);CHECK_EQ(esk_esp32_machine_run_core(m,1,1),ES_OK); + CHECK_EQ(esk_esp32_machine_round(m,events,&failed,&fault),ES_OK);CHECK_EQ(failed,2); + CHECK_EQ(events[0],0);CHECK_EQ(events[1],0);CHECK_EQ(one->cpu.a[2],0);CHECK_EQ(two->cpu.a[2],1); + CHECK_EQ(bus.read(bus.ctx,0x3ffb0000,4,&value),ES_OK);CHECK_EQ(value,1); + CHECK_EQ(esk_esp32_machine_retired(m,0),1);CHECK_EQ(esk_esp32_machine_retired(m,1),1); + /* Explicit retry on CPU1: its comparison register is private. */ + two->scompare1=1;two->cpu.a[2]=2;two->cpu.pc=0x40080000; + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK); + CHECK_EQ(one->scompare1,0);CHECK_EQ(two->scompare1,1); + CHECK_EQ(bus.read(bus.ctx,0x3ffb0000,4,&value),ES_OK);CHECK_EQ(value,2); + CHECK_EQ(esk_esp32_machine_round(m,events,&failed,&fault),ES_OK); + CHECK_EQ(one->cpu.pc,0x40080005);CHECK_EQ(two->cpu.pc,0x40080005); + /* CPU0 fault stops the round before CPU1, then stays paused. */ + CHECK(esk_esp32_machine_round(m,events,&failed,&fault)!=ES_OK);CHECK_EQ(failed,0); + CHECK_EQ(two->cpu.pc,0x40080005); + CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK);CHECK_EQ(event,19); + CHECK_EQ(esk_esp32_machine_configure(m,0,0x40080003),ES_OK); + CHECK_EQ(esk_esp32_machine_run_core(m,0,1),ES_OK); + CHECK(esk_esp32_machine_round(m,events,&failed,&fault)!=ES_OK);CHECK_EQ(failed,1); + CHECK_EQ(one->cpu.pc,0x40080005);CHECK_EQ(esk_esp32_machine_retired(m,0),1); + CHECK_EQ(bus.read(bus.ctx,0x3ffb0000,4,&value),ES_OK);CHECK_EQ(value,2); + CHECK_EQ(esk_esp32_machine_step(m,2,&event,&fault),ES_EINVAL); + CHECK(esk_esp32_machine_core(m,2)==NULL); + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +static void independent_wait_and_clock(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_xtensa_windows *one,*two; + unsigned events[2],failed,event;esk_xtensa_fault fault;unsigned i; + uint32_t general[3]={0x40081000,0x40081040,0x40081080}; + uint32_t high[6]={0x40082000,0x40082040,0x40082080,0x400820c0,0x40082100,0x40082140}; + unsigned char wait[]={0x00,0x70,0x00}; /* WAITI 0 */ + unsigned char nops[]={0x3d,0xf0,0x3d,0xf0,0x3d,0xf0}; + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK); + CHECK_EQ(esk_esp32_fabric_load(esk_esp32_machine_fabric(m),0,0x40080000,wait,sizeof wait),ES_OK); + CHECK_EQ(esk_esp32_fabric_load(esk_esp32_machine_fabric(m),0,0x40080010,nops,sizeof nops),ES_OK); + for(i=0;i<2;i++) { + esk_xtensa_windows *w; + CHECK_EQ(esk_esp32_machine_configure(m,i,i ? 0x40080010u : 0x40080000u),ES_OK); + w=esk_esp32_machine_core(m,i);esk_xtensa_exception_configure(w,general); + CHECK_EQ(esk_xtensa_irq_configure(w,high),ES_OK);w->irq.intenable=1u<<6; + CHECK_EQ(esk_xtensa_irq_write(w,240,10),ES_OK); + CHECK_EQ(esk_esp32_machine_run_core(m,i,1),ES_OK); + } + one=esk_esp32_machine_core(m,0);two=esk_esp32_machine_core(m,1); + CHECK_EQ(esk_esp32_machine_round(m,events,&failed,&fault),ES_OK); + CHECK_EQ(one->irq.waiting,1);CHECK_EQ(two->irq.waiting,0); + CHECK_EQ(esk_esp32_machine_round(m,events,&failed,&fault),ES_OK); + CHECK_EQ(events[0],18);CHECK_EQ(events[1],0); + CHECK_EQ(one->cpu.pc,0x40080003);CHECK_EQ(two->cpu.pc,0x40080014); + CHECK_EQ(esk_esp32_machine_retired(m,0),1);CHECK_EQ(esk_esp32_machine_retired(m,1),2); + CHECK_EQ(one->irq.ccount,0);CHECK_EQ(two->irq.ccount,0); + CHECK_EQ(esk_xtensa_clock_advance(one,10),ES_OK); + CHECK_EQ(two->irq.ccount,0);CHECK_EQ(esk_xtensa_irq_pending(two),0); + CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK);CHECK_EQ(event,11); + CHECK_EQ(one->irq.waiting,0);CHECK_EQ(one->epc1,0x40080003); + CHECK_EQ(one->cpu.pc,general[0]);CHECK_EQ(two->cpu.pc,0x40080014); + CHECK_EQ(esk_esp32_machine_retired(m,0),1); + /* Reconfigure only CPU0; peer clock, registers and RAM survive. */ + two->cpu.a[5]=123; + CHECK_EQ(esk_esp32_machine_configure(m,0,0x40080000),ES_OK); + CHECK_EQ(one->irq.enabled,0);CHECK_EQ(two->irq.enabled,1);CHECK_EQ(two->cpu.a[5],123); + CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK);CHECK_EQ(event,19); + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +TEST_MAIN_BEGIN + (void)t_str_eq; + RUN(shared_execution);RUN(independent_wait_and_clock); +TEST_MAIN_END diff --git a/tests/unit/test_xtensa_atomic.c b/tests/unit/test_xtensa_atomic.c index 0e5e0b717..4e56c3b39 100644 --- a/tests/unit/test_xtensa_atomic.c +++ b/tests/unit/test_xtensa_atomic.c @@ -30,7 +30,7 @@ static es_status execute(esk_xtensa_windows *w,const esk_xtensa_memory *bus,uint } static void compare_and_alias(void) { - bus_state b={0};esk_xtensa_memory bus={&b,read_mem,write_mem,cas}; + bus_state b={0};esk_xtensa_memory bus={&b,read_mem,write_mem,cas,NULL}; esk_xtensa_windows one,two;unsigned event;esk_xtensa_fault f; esk_xtensa_windows_init(&one,0x40000000,NULL);esk_xtensa_windows_init(&two,0x40001000,NULL); one.cpu.a[3]=256;one.cpu.a[2]=1;two.cpu.a[3]=256;two.cpu.a[2]=2; @@ -50,7 +50,7 @@ static void compare_and_alias(void) } static void failures_and_window_priority(void) { - bus_state b={9,0,0,0,0,0,0,0};esk_xtensa_memory bus={&b,read_mem,write_mem,cas}; + bus_state b={9,0,0,0,0,0,0,0};esk_xtensa_memory bus={&b,read_mem,write_mem,cas,NULL}; esk_xtensa_windows w,before;unsigned event,calls;esk_xtensa_fault f; esk_xtensa_windows_init(&w,0x40000000,NULL);w.cpu.a[3]=257;w.cpu.a[2]=5;w.scompare1=9;before=w; CHECK_EQ(execute(&w,&bus,0x00e322,&event,&f),ES_EINVAL);CHECK_EQ(f.kind,ESK_XT_ALIGN); diff --git a/tests/unit/test_xtensa_execute.c b/tests/unit/test_xtensa_execute.c index 2317ef024..abd02ecfa 100644 --- a/tests/unit/test_xtensa_execute.c +++ b/tests/unit/test_xtensa_execute.c @@ -42,7 +42,7 @@ static void program(void) 0x4b,0x22, 0x0b,0x33, 0x56,0x43,0xff, 0x45,0,0, 0xc6,0,0, 0x4a,0x44, 0x0d,0xf0, 0x3d,0xf0 }; - memory m = {{0},0,0,0,0}; esk_xtensa_memory bus = {&m,read_mem,write_mem,NULL}; + memory m = {{0},0,0,0,0}; esk_xtensa_memory bus = {&m,read_mem,write_mem,NULL,NULL}; esk_xtensa_state s = {{0},0}; esk_xtensa_fault f; unsigned i, steps = 0; memcpy(m.data, code, sizeof code); for (i = 0; i < 4; i++) m.data[256+i*4] = (unsigned char)(i+1); @@ -53,7 +53,7 @@ static void program(void) } static void memory_and_faults(void) { - memory m = {{0},0,0,0,0}; esk_xtensa_memory bus = {&m,read_mem,write_mem,NULL}; + memory m = {{0},0,0,0,0}; esk_xtensa_memory bus = {&m,read_mem,write_mem,NULL,NULL}; esk_xtensa_state s = {{0},0}, before; esk_xtensa_fault f; s.a[2]=UINT32_C(0x89abcdef); s.a[3]=256; CHECK_EQ(execute(&s,&bus,0x006322,3,&f),ES_OK); /* s32i a2,a3,0 */ diff --git a/tests/unit/test_xtensa_interrupts.c b/tests/unit/test_xtensa_interrupts.c index a0faf98cc..0d97d3945 100644 --- a/tests/unit/test_xtensa_interrupts.c +++ b/tests/unit/test_xtensa_interrupts.c @@ -138,7 +138,7 @@ static void fetched_handler(void) /* Save A2 in EXCSAVE3, acknowledge software input 29, restore A2, RFI 3. */ static const unsigned char handler[]={0x20,0xd3,0x13,0x22,0xa0,1,0x30,0x22,1,0x20,0xe3,0x13,0x20,0xd3,3,0x10,0x33,0}; static const uint32_t h[6]={0x1080,0x10c0,0x1100,0x1140,0x1180,0x11c0}; - unsigned char data[512]={0};esk_xtensa_memory bus={data,code_read,NULL,NULL};esk_xtensa_windows w; + unsigned char data[512]={0};esk_xtensa_memory bus={data,code_read,NULL,NULL,NULL};esk_xtensa_windows w; esk_xtensa_fault f;unsigned event,steps=0,entries=0; memcpy(data,program,sizeof program);memcpy(data+192,handler,sizeof handler); init(&w);w.cpu.pc=0x1000;CHECK_EQ(esk_xtensa_irq_configure(&w,h),ES_OK); diff --git a/tests/unit/test_xtensa_system.c b/tests/unit/test_xtensa_system.c index ad721740f..726784bdb 100644 --- a/tests/unit/test_xtensa_system.c +++ b/tests/unit/test_xtensa_system.c @@ -115,7 +115,7 @@ static void fetched_syscall_program(void) static const unsigned char program[]={0x0c,0x72,0,0x50,0,0x1b,0x22,0x3d,0xf0}; static const unsigned char handler[]={0x30,0xb1,3,0x3b,0x33,0x30,0xb1,0x13,0,0x30,0}; static const uint32_t evectors[3]={0x1040,0x1080,0x10c0}; - unsigned char data[256]={0};esk_xtensa_windows w;esk_xtensa_memory bus={data,code_read,NULL,NULL}; + unsigned char data[256]={0};esk_xtensa_windows w;esk_xtensa_memory bus={data,code_read,NULL,NULL,NULL}; esk_xtensa_fault fault;unsigned event,steps=0,entries=0; memcpy(data,program,sizeof program);memcpy(data+64,handler,sizeof handler); esk_xtensa_windows_init(&w,0x1000,NULL);esk_xtensa_exception_configure(&w,evectors); diff --git a/tests/unit/test_xtensa_windows.c b/tests/unit/test_xtensa_windows.c index 6236ad087..9947c84e5 100644 --- a/tests/unit/test_xtensa_windows.c +++ b/tests/unit/test_xtensa_windows.c @@ -132,7 +132,7 @@ static es_status mem_write(void *ctx,uint32_t address,unsigned bytes,uint32_t va static void exception_memory(void) { esk_xtensa_windows w,before; unsigned event; - test_memory m={0x89abcdef,0,0,0}; esk_xtensa_memory bus={&m,mem_read,mem_write,NULL}; + test_memory m={0x89abcdef,0,0,0}; esk_xtensa_memory bus={&m,mem_read,mem_write,NULL,NULL}; esk_xtensa_windows_init(&w,0x40000000,vectors); w.cpu.a[3]=192; before=w; CHECK_EQ(execute_bus(&w,&bus,0x090320,3,&event),ES_EUNSUPPORTED); /* L32E only in EXCM */ CHECK_EQ(m.reads,0); CHECK_EQ(memcmp(&w,&before,sizeof w),0); diff --git a/tests/vendor/README.md b/tests/vendor/README.md index 97d0eaba6..a2fb8cee0 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -76,3 +76,11 @@ division functions: 832 executions, including 64 pairs of instruction-interleave execution states forcing a CAS retry. Supply `--compiler`, `--objcopy`, `--linker` and `--nm`. Its callback is a serialized fixture, not a host-thread-safe shared SoC RAM model or proof of full SMP/coherence. + +`xtensa_integer/run_machine_c.py` uses the same actual ESP32 tools to execute two +compiled atomic increment loops through native internal RAM and CAS, without +Python memory callbacks: 256 task executions, 64 deterministic schedule replays, +65,590 instructions. It checks counts, sums, wrap, return/window/stack state and +retirement. Supply `--compiler`, `--objcopy`, `--linker`, `--nm`. This is a +serialized functional harness, not DPORT/ROM boot, cache/coherence, cycle timing +or full SMP qualification. diff --git a/tests/vendor/xtensa_integer/run_execute.py b/tests/vendor/xtensa_integer/run_execute.py index 41af49735..54f021b4a 100644 --- a/tests/vendor/xtensa_integer/run_execute.py +++ b/tests/vendor/xtensa_integer/run_execute.py @@ -33,7 +33,7 @@ class Fault(C.Structure): class Memory(C.Structure): - _fields_ = [('ctx', C.c_void_p), ('read', Read), ('write', Write), ('compare_exchange', CAS)] + _fields_ = [('ctx', C.c_void_p), ('read', Read), ('write', Write), ('compare_exchange', CAS), ('fetch', Read)] def main(): diff --git a/tests/vendor/xtensa_integer/run_machine_c.py b/tests/vendor/xtensa_integer/run_machine_c.py new file mode 100644 index 000000000..6a612e048 --- /dev/null +++ b/tests/vendor/xtensa_integer/run_machine_c.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""Two ESP32-compiled tasks over the native shared internal-RAM fabric. + +No Python memory or atomic callbacks. Instruction-interleaved functional proof; +not ROM boot, a complete SMP system, cache coherence or cycle qualification. +""" +import argparse +import ctypes as C +from pathlib import Path +import random +import subprocess +import tempfile +from run_execute import Fault, Memory, ROOT, MASK +from run_windows_c import Windows + +SOURCE = ''' +unsigned task(volatile unsigned *counter, unsigned count) { + unsigned sum=0; + while(count--) sum+=__sync_add_and_fetch(counter,1); + return sum; +} +''' + + +class Allocator(C.Structure): + _fields_ = [('hooks', C.c_void_p * 4), ('live_bytes', C.c_size_t), + ('allocations', C.c_size_t)] + + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + for name in ['compiler', 'objcopy', 'linker', 'nm']: + ap.add_argument('--' + name, required=True) + ap.add_argument('--cc', default='cc') + args = ap.parse_args() + rng = random.Random(0x32c0) + with tempfile.TemporaryDirectory(prefix='embsim-machine-') as tmp: + out = Path(tmp) + src, obj, elf, binary = [out / ('task.' + x) for x in ['c', 'o', 'elf', 'bin']] + src.write_text(SOURCE, encoding='utf-8') + subprocess.run([args.compiler, '-B' + str(Path(args.compiler).parent) + '/', + '-O1', '-ffreestanding', '-fno-builtin', '-c', str(src), '-o', str(obj)], check=True) + subprocess.run([args.linker, '--no-relax', '-Ttext=0x40080000', '--entry=task', + str(obj), '-o', str(elf)], check=True) + subprocess.run([args.objcopy, '-O', 'binary', '--only-section=.text', str(elf), str(binary)], check=True) + symbols = {f[2]: int(f[0], 16) for line in subprocess.check_output([args.nm, str(elf)], text=True).splitlines() + if len(f := line.split()) == 3} + lib = out / 'machine.so' + sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c']] + sources += [ROOT / 'core/util/alloc.c'] + subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', + '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), + *map(str, sources), '-o', str(lib)], check=True) + api = C.CDLL(str(lib)) + def bind(name, result, *params): + fn = getattr(api, name); fn.restype = result; fn.argtypes = list(params); return fn + pointer = C.c_void_p + alloc_init = bind('esk_alloc_init', None, C.POINTER(Allocator), pointer) + create = bind('esk_esp32_machine_create', C.c_int, C.POINTER(Allocator), C.POINTER(pointer)) + destroy = bind('esk_esp32_machine_destroy', None, pointer) + fabric = bind('esk_esp32_machine_fabric', pointer, pointer) + bus_get = bind('esk_esp32_fabric_bus', Memory, pointer, C.c_uint) + load = bind('esk_esp32_fabric_load', C.c_int, pointer, C.c_uint, C.c_uint32, pointer, C.c_size_t) + configure = bind('esk_esp32_machine_configure', C.c_int, pointer, C.c_uint, C.c_uint32) + core = bind('esk_esp32_machine_core', C.POINTER(Windows), pointer, C.c_uint) + run = bind('esk_esp32_machine_run_core', C.c_int, pointer, C.c_uint, C.c_int) + step = bind('esk_esp32_machine_step', C.c_int, pointer, C.c_uint, C.POINTER(C.c_uint), C.POINTER(Fault)) + retired = bind('esk_esp32_machine_retired', C.c_uint64, pointer, C.c_uint) + code = binary.read_bytes(); total = 0; runs = 0 + # Repeat each initial condition/schedule to prove deterministic replay. + for trial in range(64): + initial = [0, MASK - 30, 0x80000000][trial] if trial < 3 else rng.getrandbits(32) + counts = [rng.randrange(1, 33), rng.randrange(1, 33)] + trace = None + for replay in range(2): + allocator = Allocator(); alloc_init(C.byref(allocator), None) + machine = pointer(); assert create(C.byref(allocator), C.byref(machine)) == 0 + try: + ram = fabric(machine); bus = bus_get(ram, 0) + assert load(ram, 0, 0x40080000, C.c_char_p(code), len(code)) == 0 + assert bus.write(bus.ctx, 0x3ffb0000, 4, initial) == 0 + stop = 0x7ffffff0; states = [] + for cpu in range(2): + assert configure(machine, cpu, symbols['task']) == 0 + w = core(machine, cpu).contents + w.ps |= 1 << 16; w.cpu.a[4] = stop + w.cpu.a[0] = 0x12340000 + cpu; w.cpu.a[1] = 0x3ffd0000 + cpu * 4096 + w.cpu.a[6] = 0x3ffb0000; w.cpu.a[7] = counts[cpu] + states.append(w); assert run(machine, cpu, 1) == 0 + done = [False, False]; event = C.c_uint(); fault = Fault(); schedule = [] + for turn in range(30000): + cpu = (turn + trial) % 2 + if done[cpu]: + continue + w = states[cpu] + if w.cpu.pc == stop: + done[cpu] = True; assert run(machine, cpu, 0) == 0 + if all(done): + break + continue + status = step(machine, cpu, C.byref(event), C.byref(fault)) + assert status == 0 and event.value == 0, (status, cpu, hex(fault.pc), hex(fault.opcode), fault.kind, event.value) + schedule.append((cpu, w.cpu.pc)); total += 1 + assert all(done), 'tasks did not finish' + value = C.c_uint32(); assert bus.read(bus.ctx, 0x3ffb0000, 4, C.byref(value)) == 0 + n = sum(counts); assert value.value == (initial + n) & MASK + # Every successful increment returns a distinct consecutive value. + assert sum(w.cpu.a[6] for w in states) & MASK == (n * initial + n * (n + 1) // 2) & MASK + for cpu, w in enumerate(states): + assert w.base == 0 and w.start == 1 + assert w.cpu.a[0] == 0x12340000 + cpu and w.cpu.a[1] == 0x3ffd0000 + cpu * 4096 + assert retired(machine, cpu) == sum(1 for owner, _ in schedule if owner == cpu) + result = (schedule, value.value, [w.cpu.a[6] for w in states]) + if replay: + assert result == trace + else: + trace = result + runs += 2 + finally: + destroy(machine) + assert allocator.live_bytes == 0 + print(f'{runs} compiled task executions, 64 schedule replays, {total} instructions; native shared RAM/CAS') + + +if __name__ == '__main__': + main() From 3c6ab7f7c59beaa232894fcf2ac7949a22bab84d Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 01:42:13 +0000 Subject: [PATCH 12/57] Execute Xtensa acquire release operations and qualify shared-memory spinlocks --- CHANGELOG.md | 18 ++++-- core/mcu/xtensa/execute.c | 6 +- core/mcu/xtensa/execute.h | 4 ++ core/mcu/xtensa/windows.c | 2 +- docs/STATUS.md | 10 +++- docs/engineering/esp32-dual-core.md | 38 +++++++++--- tests/unit/test_xtensa_ordering.c | 63 ++++++++++++++++++++ tests/vendor/README.md | 9 +-- tests/vendor/xtensa_integer/run_execute.py | 2 +- tests/vendor/xtensa_integer/run_machine_c.py | 22 +++++-- 10 files changed, 146 insertions(+), 28 deletions(-) create mode 100644 tests/unit/test_xtensa_ordering.c diff --git a/CHANGELOG.md b/CHANGELOG.md index c434d484a..3ff2c990b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental Xtensa acquire/release and shared spinlocks + +- Added L32AI/S32RI with scaled offsets, alias-safe access, alignment/failure + retention and window-overflow priority. Synchronous completed bus callbacks + supply functional ordering; asynchronous transactions/caches remain unsupported. +- Added 30,732 native assertions and expanded assembled execution checks to + 4,800 cases/1,152 refused accesses. Native shared-bus compiled acceptance now + includes spinlocks protecting ordinary writes: 512 task executions, 128 exact + schedule replays and 229,322 instructions across both workloads. + ### Experimental ESP32 internal memory and two-context harness - Added native SRAM0/1/2 and RTC memory views, including SRAM1's reversed @@ -16,8 +26,8 @@ versions follow semantic versioning. There is no release yet. contexts with shared RAM, explicit pause/resume, fault isolation and private interrupt/clock state. No guessed clock ticks or silicon reset defaults. - Added exhaustive SRAM1 alias/boundary/failure fixtures and native-bus acceptance - with actual ESP32 compiled tasks: 256 executions, 64 deterministic schedule - replays and 65,590 instructions. This is not a runnable board target, ROM boot, + with actual ESP32 compiled tasks: 512 executions, 128 deterministic schedule + replays and 229,322 instructions. This is not a runnable board target, ROM boot, DPORT/cache/coherence implementation, host-thread safety or complete SMP. ### Experimental Xtensa integer and conditional-store extension @@ -30,7 +40,7 @@ versions follow semantic versioning. There is no release yet. no read/write fallback. Full SMP, shared SoC RAM/coherence, memory-type policy and implementation-dependent spurious failures remain unqualified. - Added 128 arithmetic and 67 atomic assertions; expanded assembler cases to - 9,728 integer and 4,608 flow/bit branches. Actual ESP32 compiled atomics/division + 9,728 integer and 4,800 flow/bit branches. Actual ESP32 compiled atomics/division pass 832 executions, including 64 interleaved pairs forcing CAS retry. ### Experimental ESP32 CPU interrupts and compare clock @@ -82,7 +92,7 @@ versions follow semantic versioning. There is no release yet. - Preserve PC/registers on unsupported or failed accesses and report failing PC/address/opcode; reject unaligned data accesses before touching the bus. - Added assembled loop/subroutine acceptance, native boundary/fault checks and - 3,840 externally assembled execution cases plus 960 failed-access cases. + 3,840 externally assembled execution cases plus 1,152 failed-access cases. - Immediate shifts, EXTUI and signed/unsigned immediate comparisons allow compiler-generated sum/sort/copy routines to run: 96 executions verify complete buffers, guards, CALL0 callee-save behavior and returns. diff --git a/core/mcu/xtensa/execute.c b/core/mcu/xtensa/execute.c index d1178984b..d36288921 100644 --- a/core/mcu/xtensa/execute.c +++ b/core/mcu/xtensa/execute.c @@ -87,10 +87,12 @@ static es_status execute_inner(esk_xtensa_state *state, const esk_xtensa_memory if (taken) { next.pc = pc + 4 + ((t & 3u) << 4 | r); flow = 0; } } else if (op == 8 || op == 9) { bytes = 4; addr = next.a[s] + (uint32_t)r * 4; store = op == 9; - } else if (op == 2 && (r <= 2 || r == 4 || r == 5 || r == 6 || r == 9)) { + } else if (op == 2 && (r <= 2 || r == 4 || r == 5 || r == 6 || r == 9 || r == 11 || r == 15)) { unsigned scale = (r == 0 || r == 4) ? 0u : (r == 1 || r == 5 || r == 9) ? 1u : 2u; bytes = 1u << scale; addr = next.a[s] + ((word >> 16) << scale); - store = r >= 4 && r <= 6; signed16 = r == 9; + /* Synchronous completed callbacks already satisfy L32AI acquisition + * and S32RI release ordering. No queued/cache bus is supported here. */ + store = (r >= 4 && r <= 6) || r == 15; signed16 = r == 9; } else if (op == 1) { /* L32R without extended LITBASE option */ bytes = 4; addr = ((pc + 3) & UINT32_C(0xfffffffc)) + ((word >> 8 | UINT32_C(0xffff0000)) << 2); } else return fail(fault, ESK_XT_UNSUPPORTED, pc, pc, word); diff --git a/core/mcu/xtensa/execute.h b/core/mcu/xtensa/execute.h index d3429e7b6..b9179fc51 100644 --- a/core/mcu/xtensa/execute.h +++ b/core/mcu/xtensa/execute.h @@ -9,6 +9,10 @@ * to the caller with PC/address/opcode, not dispatched to architectural vectors. * Sequential callbacks provide ordering but make no multicore ordering claim. * Callback failure MUST mean no completed write. No rollback of MMIO reads. + * Reads/writes must finish synchronously before returning; posted operations, + * stale private caches and deferred visibility require a different interface. + * This serialized provider contract supplies L32AI/S32RI ordering, not a + * pipeline/cache/coherence model or an asynchronous system-bus barrier. * Callbacks must not re-enter or mutate the executing CPU state. Queue its * input/clock changes for instruction boundaries; these APIs are not thread-safe. */ typedef struct esk_xtensa_state { uint32_t a[16], pc; } esk_xtensa_state; diff --git a/core/mcu/xtensa/windows.c b/core/mcu/xtensa/windows.c index 306a23cb3..e2857f828 100644 --- a/core/mcu/xtensa/windows.c +++ b/core/mcu/xtensa/windows.c @@ -95,7 +95,7 @@ static es_status references(const unsigned char *code, unsigned width, uint32_t else if (op==6 && ((t&3u)==1 || t==2 || t==6 || t==10 || t==14 || t==11 || t==15)) *mask=bit(s); else if (op==7) *mask=(r==6 || r==7 || r==14 || r==15) ? bit(s) : bit(s)|bit(t); else if (op==12 && t>=8) *mask=bit(s); - else if (op==8 || op==9 || (op==2 && (r<=2 || r==4 || r==5 || r==6 || r==9))) *mask=bit(s)|bit(t); + else if (op==8 || op==9 || (op==2 && (r<=2 || r==4 || r==5 || r==6 || r==9 || r==11 || r==15))) *mask=bit(s)|bit(t); else if (op==1) *mask=bit(t); else return ES_EUNSUPPORTED; return ES_OK; diff --git a/docs/STATUS.md b/docs/STATUS.md index 3b89d321e..eb1c97209 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -21,7 +21,7 @@ window: 111 native assertions and 9,728 separately assembled all-register compar pass. Mixed-width fetch, compact arithmetic, non-windowed calls/returns, conditional branches and callback memory access now execute an assembled summing/subroutine program. The execution test passes 618 assertions; a separate assembler/linker -referee passes 4,608 execution cases and 960 failed accesses. Complete +referee passes 4,800 execution cases and 1,152 failed accesses. Complete exception/interrupt behavior, full ISA, SMP, SoC boot and timing remain unimplemented. The new 64-AR window engine implements windowed calls/ENTRY/RETW, lazy spill/fill exception transitions, RFWO/RFWU retries, selected special @@ -52,9 +52,13 @@ word-reversed SRAM1 instruction aliases, PRO-only RTC fast RAM, shared RTC slow RAM, optional external ROM and per-core host-controlled boot remap. A serialized two-context harness shares this RAM while keeping CPU/interrupt/clock state private. Native fixtures cover address/permission/remap/fault/wait boundaries; -256 actual ESP32 compiled task executions and 64 replay comparisons pass through -the native bus (65,590 instructions). This remains a bounded memory/execution +512 actual ESP32 compiled task executions and 128 replay comparisons pass through +the native bus (229,322 instructions). This remains a bounded memory/execution harness: cache/MMU, DPORT hardware controls, complete SMP and SoC boot are absent. +L32AI/S32RI now provide acquire/release ordering under the synchronous completed +callback contract (30,732 native assertions). The compiled shared-bus acceptance +includes acquire/CAS/release spinlocks protecting ordinary writes. Asynchronous +transactions, cache coherence and physical timing remain unsupported. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 78ac17839..8b3b20365 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -101,8 +101,8 @@ loop, calls a doubling subroutine and returns to the expected PC (27 executed instructions). The native test has 618 assertions covering branches, compact immediate limits, byte/halfword/word behavior, literal loads, link-register alias, 32-bit PC wrap, memory-edge fetches, alignment and failed-access state retention. -An optional external-assembler/linker referee checks 4,608 execution cases and -960 rejected accesses against independent Python PC/register/bus expectations. +An optional external-assembler/linker referee checks 4,800 execution cases and +1,152 rejected accesses against independent Python PC/register/bus expectations. Linker relaxation is disabled to preserve the fixture's explicit instruction addresses. The earlier 9,728 integer cases continue to pass. @@ -426,8 +426,8 @@ success/failure comparisons, base/result alias, offset wrap, unsupported regions missing callbacks, fault retention and exactly one callback without normal bus read/write fallback. Existing system/window/interrupt/timer checks still pass. -The expanded assembler referees now pass 9,728 integer cases and 4,608 flow/bit- -branch cases, plus the existing 960 rejected accesses. Actual ESP32 GCC compiles +The expanded assembler referees now pass 9,728 integer cases and 4,800 flow/bit- +branch cases, plus the existing 1,152 rejected accesses. Actual ESP32 GCC compiles exchange, compare-and-swap, increment and signed/unsigned division/remainder C routines for 832 successful executions (5,248 fetched instructions). Sixty-four pairs of execution states share test RAM and alternate instruction steps so both @@ -444,6 +444,28 @@ python tests/vendor/xtensa_integer/run_atomic_c.py \ --nm /path/to/xtensa-esp32-elf-nm ``` +## Implemented: synchronous acquire/release memory operations + +L32AI and S32RI implement aligned 32-bit load-acquire and store-release with an +unsigned 8-bit word-scaled offset (0..1020 bytes). Address/source aliasing uses +the pre-instruction registers; window-reference checks precede bus access. +Misalignment or refused callbacks preserve architectural state and report the +existing load/store fault diagnostic rather than inventing an MMU exception. + +The callback provider must complete reads/writes before returning and expose +completed effects to subsequent turns. This serialized functional contract +already supplies the required acquire/release ordering. It does not support +posted writes, stale private caches or asynchronous transactions; extending the +bus to those requires explicit completion/barrier semantics. No physical cache, +pipeline or timing fidelity follows from this implementation. + +`test_xtensa_ordering` passes 30,732 assertions across every register pair, +minimum/maximum offsets, failure retention, alignment and window-overflow +priority. The independent actual ESP32 assembler runner now passes 4,800 +execution cases and 1,152 refused accesses, including acquire/release. +The native machine runner below additionally executes actual compiler-generated +L32AI/S32RI in spinlocks protecting non-atomic read/modify/write critical sections. + ## Implemented: internal RAM and two-context execution harness `core/mcu/esp32/fabric.c` implements a bounded native memory provider from @@ -500,11 +522,11 @@ SRAM1 word and byte lane, remap isolation, RTC restrictions, rejected access widths/holes, all-or-nothing loading, allocation failure, shared conditional stores, independent state, fault ordering and one-core WAITI/timer wake while its peer continues. The separate compiled runner -uses no Python memory/CAS callbacks: actual ESP32 GCC output runs two increment -loops against native shared RAM, checking final count, the sum of distinct +uses no Python memory/CAS callbacks: actual ESP32 GCC output runs atomic counter +loops and acquire/CAS/release spinlocks protecting ordinary shared writes, checking final count, the sum of distinct returned increments, unsigned wrap, caller/window/stack preservation, retirement -and exact replay equality. It passes 256 task executions and 64 schedule replays -(65,590 instructions). This is functional interleaving evidence, not full SMP +and exact replay equality. It passes 512 task executions and 128 schedule replays +(229,322 instructions). This is functional interleaving evidence, not full SMP or real-hardware qualification. ```sh diff --git a/tests/unit/test_xtensa_ordering.c b/tests/unit/test_xtensa_ordering.c new file mode 100644 index 000000000..93821c85a --- /dev/null +++ b/tests/unit/test_xtensa_ordering.c @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/xtensa/windows.h" +typedef struct fixture { uint32_t address,value;unsigned reads,writes,fail; } fixture; +static es_status read_word(void *ctx,uint32_t address,unsigned bytes,uint32_t *value) +{ + fixture *f=(fixture *)ctx;f->reads++;f->address=address;*value=f->value; + return f->fail || bytes!=4 ? ES_EINVAL : ES_OK; +} +static es_status write_word(void *ctx,uint32_t address,unsigned bytes,uint32_t value) +{ + fixture *f=(fixture *)ctx;f->writes++;f->address=address; + if(f->fail || bytes!=4) return ES_EINVAL; + f->value=value;return ES_OK; +} +static es_status execute(esk_xtensa_windows *w,esk_xtensa_memory *bus,unsigned t,unsigned s,unsigned offset,int store,unsigned *event,esk_xtensa_fault *fault) +{ + unsigned char code[3];size_t used=99;es_status st; + code[0]=(unsigned char)((t<<4)|2u);code[1]=(unsigned char)((store ? 0xf0u : 0xb0u)|s);code[2]=(unsigned char)offset; + st=esk_xtensa_window_execute(w,bus,code,3,&used,event,fault); + CHECK_EQ(used,st==ES_OK && !*event ? 3u : 0u);return st; +} +static void registers_offsets_and_faults(void) +{ + unsigned t,s,k,store,event;esk_xtensa_fault fault; + for(t=0;t<16;t++) for(s=0;s<16;s++) for(k=0;k<3;k++) for(store=0;store<2;store++) { + esk_xtensa_windows w,before;fixture f={0,0x89abcdef,0,0,0};uint32_t expected,address; + esk_xtensa_memory bus={&f,read_word,write_word,NULL,NULL}; + unsigned offset=k==2 ? 255u : k; + esk_xtensa_windows_init(&w,0x40080000,NULL); + w.cpu.a[t]=0x12345678;w.cpu.a[s]=0x3ffb0000; + expected=store ? w.cpu.a[t] : f.value;address=w.cpu.a[s]+offset*4u; + CHECK_EQ(execute(&w,&bus,t,s,offset,(int)store,&event,&fault),ES_OK);CHECK_EQ(event,0); + CHECK_EQ(f.address,address);CHECK_EQ(f.reads,store ? 0u : 1u);CHECK_EQ(f.writes,store); + CHECK_EQ(w.cpu.pc,0x40080003); + if(store) CHECK_EQ(f.value,expected);else CHECK_EQ(w.cpu.a[t],expected); + /* Failed callbacks and misalignment do not commit architectural state. */ + f.fail=1;w.cpu.a[s]=0x3ffb0000;f.reads=f.writes=0;before=w; + CHECK_EQ(execute(&w,&bus,t,s,offset,(int)store,&event,&fault),ES_EINVAL); + CHECK_EQ(memcmp(&w,&before,sizeof w),0); + CHECK_EQ(fault.kind,store ? ESK_XT_STORE : ESK_XT_LOAD); + CHECK_EQ(f.reads,store ? 0u : 1u);CHECK_EQ(f.writes,store); + f.reads=f.writes=0;w.cpu.a[s]=0x3ffb0001;before=w; + CHECK_EQ(execute(&w,&bus,t,s,offset,(int)store,&event,&fault),ES_EINVAL); + CHECK_EQ(fault.kind,ESK_XT_ALIGN);CHECK_EQ(f.reads,0);CHECK_EQ(f.writes,0); + CHECK_EQ(memcmp(&w,&before,sizeof w),0); + } +} +static void window_priority(void) +{ + unsigned store,event;esk_xtensa_fault fault; + uint32_t vectors[6]={0x40081000,0x40081100,0x40081200,0x40081300,0x40081400,0x40081500}; + for(store=0;store<2;store++) { + esk_xtensa_windows w;fixture f={0,0,0,0,0};esk_xtensa_memory bus={&f,read_word,write_word,NULL,NULL}; + esk_xtensa_windows_init(&w,0x40080000,vectors);w.start=7; + CHECK_EQ(execute(&w,&bus,8,3,0,(int)store,&event,&fault),ES_OK); + CHECK_EQ(event,1);CHECK_EQ(w.cpu.pc,vectors[0]);CHECK_EQ(f.reads,0);CHECK_EQ(f.writes,0); + } +} +TEST_MAIN_BEGIN + (void)t_str_eq;RUN(registers_offsets_and_faults);RUN(window_priority); +TEST_MAIN_END diff --git a/tests/vendor/README.md b/tests/vendor/README.md index a2fb8cee0..4cf96845e 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -32,7 +32,7 @@ not qualify the complete ESP32 LX6 configuration. See [scope and reproduction](../../docs/engineering/esp32-dual-core.md). `xtensa_integer/run_execute.py` additionally uses a separate linker, with -relaxation disabled, for 4,608 flow/memory/density cases and 960 rejected bus +relaxation disabled, for 4,800 flow/memory/density cases and 1,152 rejected bus accesses. Supply `--assembler`, `--objcopy` and `--linker` paths. It compares all registers, PC and exact bus transactions, including failed-access retention. It does not establish architectural exception delivery or complete ISA fidelity. @@ -78,9 +78,10 @@ and `--nm`. Its callback is a serialized fixture, not a host-thread-safe shared SoC RAM model or proof of full SMP/coherence. `xtensa_integer/run_machine_c.py` uses the same actual ESP32 tools to execute two -compiled atomic increment loops through native internal RAM and CAS, without -Python memory callbacks: 256 task executions, 64 deterministic schedule replays, -65,590 instructions. It checks counts, sums, wrap, return/window/stack state and +compiled atomic counters and spinlocks protecting ordinary writes through native +internal RAM, acquire/release operations and CAS, without +Python memory callbacks: 512 task executions, 128 deterministic schedule replays, +229,322 instructions. It checks counts, sums, wrap, return/window/stack state and retirement. Supply `--compiler`, `--objcopy`, `--linker`, `--nm`. This is a serialized functional harness, not DPORT/ROM boot, cache/coherence, cycle timing or full SMP qualification. diff --git a/tests/vendor/xtensa_integer/run_execute.py b/tests/vendor/xtensa_integer/run_execute.py index 54f021b4a..af905cae9 100644 --- a/tests/vendor/xtensa_integer/run_execute.py +++ b/tests/vendor/xtensa_integer/run_execute.py @@ -47,7 +47,7 @@ def main(): cases = [] operations = ['j', 'call0', 'jx', 'callx0', 'ret', 'ret.n', 'nop', 'nop.n', 'beqz', 'bnez', 'bltz', 'bgez', 'beq', 'bne', 'blt', 'bge', 'bltu', 'bgeu', - 'beqz.n', 'bnez.n', 'bany','bnone','ball','bnall','bbc','bbs','bbci','bbsi', 'beqi', 'bnei', 'blti', 'bgei', 'bltui', 'bgeui', 'l8ui', 'l16ui', 'l16si', 'l32i', 's8i', 's16i', 's32i', + 'beqz.n', 'bnez.n', 'bany','bnone','ball','bnall','bbc','bbs','bbci','bbsi', 'beqi', 'bnei', 'blti', 'bgei', 'bltui', 'bgeui', 'l8ui', 'l16ui', 'l16si', 'l32i', 'l32ai', 's32ri', 's8i', 's16i', 's32i', 'l32i.n', 's32i.n', 'l32r', 'movi.n', 'mov.n', 'add.n', 'addi.n'] signed = lambda v: v if v < 2**31 else v-2**32 for operation in operations: diff --git a/tests/vendor/xtensa_integer/run_machine_c.py b/tests/vendor/xtensa_integer/run_machine_c.py index 6a612e048..2e0c2f972 100644 --- a/tests/vendor/xtensa_integer/run_machine_c.py +++ b/tests/vendor/xtensa_integer/run_machine_c.py @@ -21,6 +21,16 @@ while(count--) sum+=__sync_add_and_fetch(counter,1); return sum; } +unsigned task_locked(volatile unsigned *counter, unsigned count, volatile unsigned *lock) { + unsigned sum=0; + while(count--) { + while(__atomic_load_n(lock,__ATOMIC_ACQUIRE) || __sync_lock_test_and_set(lock,1)) { } + *counter=*counter+1; + sum+=*counter; + __sync_lock_release(lock); + } + return sum; +} ''' @@ -71,7 +81,7 @@ def bind(name, result, *params): retired = bind('esk_esp32_machine_retired', C.c_uint64, pointer, C.c_uint) code = binary.read_bytes(); total = 0; runs = 0 # Repeat each initial condition/schedule to prove deterministic replay. - for trial in range(64): + for trial in range(128): initial = [0, MASK - 30, 0x80000000][trial] if trial < 3 else rng.getrandbits(32) counts = [rng.randrange(1, 33), rng.randrange(1, 33)] trace = None @@ -84,11 +94,11 @@ def bind(name, result, *params): assert bus.write(bus.ctx, 0x3ffb0000, 4, initial) == 0 stop = 0x7ffffff0; states = [] for cpu in range(2): - assert configure(machine, cpu, symbols['task']) == 0 + assert configure(machine, cpu, symbols['task' if trial < 64 else 'task_locked']) == 0 w = core(machine, cpu).contents w.ps |= 1 << 16; w.cpu.a[4] = stop w.cpu.a[0] = 0x12340000 + cpu; w.cpu.a[1] = 0x3ffd0000 + cpu * 4096 - w.cpu.a[6] = 0x3ffb0000; w.cpu.a[7] = counts[cpu] + w.cpu.a[6] = 0x3ffb0000; w.cpu.a[7] = counts[cpu]; w.cpu.a[8] = 0x3ffb0004 states.append(w); assert run(machine, cpu, 1) == 0 done = [False, False]; event = C.c_uint(); fault = Fault(); schedule = [] for turn in range(30000): @@ -107,13 +117,15 @@ def bind(name, result, *params): assert all(done), 'tasks did not finish' value = C.c_uint32(); assert bus.read(bus.ctx, 0x3ffb0000, 4, C.byref(value)) == 0 n = sum(counts); assert value.value == (initial + n) & MASK + final_value = value.value + assert bus.read(bus.ctx, 0x3ffb0004, 4, C.byref(value)) == 0 and value.value == 0 # Every successful increment returns a distinct consecutive value. assert sum(w.cpu.a[6] for w in states) & MASK == (n * initial + n * (n + 1) // 2) & MASK for cpu, w in enumerate(states): assert w.base == 0 and w.start == 1 assert w.cpu.a[0] == 0x12340000 + cpu and w.cpu.a[1] == 0x3ffd0000 + cpu * 4096 assert retired(machine, cpu) == sum(1 for owner, _ in schedule if owner == cpu) - result = (schedule, value.value, [w.cpu.a[6] for w in states]) + result = (schedule, final_value, [w.cpu.a[6] for w in states]) if replay: assert result == trace else: @@ -122,7 +134,7 @@ def bind(name, result, *params): finally: destroy(machine) assert allocator.live_bytes == 0 - print(f'{runs} compiled task executions, 64 schedule replays, {total} instructions; native shared RAM/CAS') + print(f'{runs} compiled task executions, 128 schedule replays (atomic counters and spinlock-protected ordinary writes), {total} instructions; native shared RAM/CAS') if __name__ == '__main__': From 3bfc26dca1ec9b16bf25e4e9040ca2f44f5b5d7f Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 01:42:46 +0000 Subject: [PATCH 13/57] Keep acquire release fault assertions unsigned for MSVC --- tests/unit/test_xtensa_ordering.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_xtensa_ordering.c b/tests/unit/test_xtensa_ordering.c index 93821c85a..cc92b40ce 100644 --- a/tests/unit/test_xtensa_ordering.c +++ b/tests/unit/test_xtensa_ordering.c @@ -39,7 +39,7 @@ static void registers_offsets_and_faults(void) f.fail=1;w.cpu.a[s]=0x3ffb0000;f.reads=f.writes=0;before=w; CHECK_EQ(execute(&w,&bus,t,s,offset,(int)store,&event,&fault),ES_EINVAL); CHECK_EQ(memcmp(&w,&before,sizeof w),0); - CHECK_EQ(fault.kind,store ? ESK_XT_STORE : ESK_XT_LOAD); + CHECK_EQ(fault.kind,(unsigned)(store ? ESK_XT_STORE : ESK_XT_LOAD)); CHECK_EQ(f.reads,store ? 0u : 1u);CHECK_EQ(f.writes,store); f.reads=f.writes=0;w.cpu.a[s]=0x3ffb0001;before=w; CHECK_EQ(execute(&w,&bus,t,s,offset,(int)store,&event,&fault),ES_EINVAL); From 47f077b56d69762ae45bd2857e4e577b046ebc49 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 02:29:30 +0000 Subject: [PATCH 14/57] Route ESP32 DPORT interrupts and execute cross-core firmware mailboxes --- CHANGELOG.md | 15 ++ core/mcu/esp32/machine.c | 73 ++++++++- core/mcu/esp32/machine.h | 13 +- core/mcu/esp32/matrix.c | 90 +++++++++++ core/mcu/esp32/matrix.h | 31 ++++ docs/STATUS.md | 15 +- docs/engineering/esp32-dual-core.md | 97 +++++++++++- tests/unit/test_esp32_ipi.c | 102 +++++++++++++ tests/unit/test_esp32_matrix.c | 67 +++++++++ tests/vendor/README.md | 15 ++ tests/vendor/xtensa_integer/run_ipi_c.py | 149 +++++++++++++++++++ tests/vendor/xtensa_integer/run_machine_c.py | 2 +- tests/vendor/xtensa_integer/run_matrix.py | 131 ++++++++++++++++ 13 files changed, 788 insertions(+), 12 deletions(-) create mode 100644 core/mcu/esp32/matrix.c create mode 100644 core/mcu/esp32/matrix.h create mode 100644 tests/unit/test_esp32_ipi.c create mode 100644 tests/unit/test_esp32_matrix.c create mode 100644 tests/vendor/xtensa_integer/run_ipi_c.py create mode 100644 tests/vendor/xtensa_integer/run_matrix.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ff2c990b..035c6a20d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental ESP32 interrupt matrix and cross-core signaling + +- Added per-core DPORT source maps/status, shared software-generated interrupts, + OR routing, internal-input disconnection, core-local GPIO inputs and explicit + NMI masking. Reserved sources/holes remain unsupported; peripheral generators + and PID hardware are not supplied by this controller. +- Unified machine data access now routes MMIO and delivers inputs after CPU + instruction commit. A peer can wake from WAITI, acknowledge a source and RFI; + paused-peer edges and same-core MMIO IRQ updates survive correctly. No physical + APB timing, DPORT reset/stall, cache/flash, full SMP or boot claim is added. +- Added 64,374 matrix/101 integration assertions and pinned-header referees for + 4,288 routes/512 combined-source cases. Actual ESP32 C sender/assembled ISR + passes 128 mailbox runs, 2,112 interrupts and 64 exact replays (188,480 retired + instructions), preserving integer registers and stack guards. + ### Experimental Xtensa acquire/release and shared spinlocks - Added L32AI/S32RI with scaled offsets, alias-safe access, alignment/failure diff --git a/core/mcu/esp32/machine.c b/core/mcu/esp32/machine.c index 05e7c36f5..1ffd533d8 100644 --- a/core/mcu/esp32/machine.c +++ b/core/mcu/esp32/machine.c @@ -2,10 +2,75 @@ /* Copyright 2026 Junior Deogracias */ #include "mcu/esp32/machine.h" #include +struct machine_view { struct esk_esp32_machine *m;unsigned cpu; }; struct esk_esp32_machine { esk_alloc *a;esk_esp32_fabric *fabric; - esk_xtensa_windows cpu[2];uint64_t retired[2];unsigned running[2]; + esk_xtensa_windows cpu[2];uint64_t retired[2];unsigned running[2],executing; + esk_esp32_matrix matrix;struct machine_view view[2]; }; +/* Never mutate an executing CPU from a bus callback. Synchronize the + * combinatorial routing only outside execution or after instruction commit. + * Both cores are updated, including a debug-paused peer, so edge pulses + * across separate retired writes are latched even while the peer is paused. */ +static void sync_inputs(esk_esp32_machine *m) +{ + unsigned cpu,input;const uint32_t internal=UINT32_C(0x200188c0); + for(cpu=0;cpu<2;cpu++) if(m->cpu[cpu].irq.enabled) { + uint32_t levels=esk_esp32_matrix_levels(&m->matrix,cpu); + for(input=0;input<32;input++) { + uint32_t bit=UINT32_C(1)<cpu[cpu],input,(levels&bit)!=0); + } + } +} +static es_status bus_read(void *ctx,uint32_t address,unsigned bytes,uint32_t *value) +{ + struct machine_view *v=(struct machine_view *)ctx;esk_xtensa_memory ram;es_status st; + st=esk_esp32_matrix_read(&v->m->matrix,address,bytes,value); + if(st!=ES_EUNSUPPORTED) return st; + ram=esk_esp32_fabric_bus(v->m->fabric,v->cpu);return ram.read(ram.ctx,address,bytes,value); +} +static es_status bus_write(void *ctx,uint32_t address,unsigned bytes,uint32_t value) +{ + struct machine_view *v=(struct machine_view *)ctx;esk_xtensa_memory ram;es_status st; + st=esk_esp32_matrix_write(&v->m->matrix,address,bytes,value); + if(st!=ES_EUNSUPPORTED) { + if(st==ES_OK && !v->m->executing) sync_inputs(v->m); + return st; + } + ram=esk_esp32_fabric_bus(v->m->fabric,v->cpu);return ram.write(ram.ctx,address,bytes,value); +} +static es_status bus_fetch(void *ctx,uint32_t address,unsigned bytes,uint32_t *value) +{ + struct machine_view *v=(struct machine_view *)ctx;esk_xtensa_memory ram=esk_esp32_fabric_bus(v->m->fabric,v->cpu); + return ram.fetch(ram.ctx,address,bytes,value); +} +static es_status bus_cas(void *ctx,uint32_t address,uint32_t expected,uint32_t desired,uint32_t *observed) +{ + struct machine_view *v=(struct machine_view *)ctx;esk_xtensa_memory ram=esk_esp32_fabric_bus(v->m->fabric,v->cpu); + return ram.compare_exchange(ram.ctx,address,expected,desired,observed); +} +esk_xtensa_memory esk_esp32_machine_bus(esk_esp32_machine *m,unsigned cpu) +{ + esk_xtensa_memory bus={NULL,NULL,NULL,NULL,NULL}; + if(m && cpu<2) { bus.ctx=&m->view[cpu];bus.read=bus_read;bus.write=bus_write;bus.fetch=bus_fetch;bus.compare_exchange=bus_cas; } + return bus; +} +es_status esk_esp32_machine_source(esk_esp32_machine *m,unsigned source,int asserted) +{ + es_status st;if(!m || m->executing) return ES_EINVAL; + st=esk_esp32_matrix_source(&m->matrix,source,asserted);if(st==ES_OK) sync_inputs(m);return st; +} +es_status esk_esp32_machine_gpio_irq(esk_esp32_machine *m,unsigned cpu,int nmi,int asserted) +{ + es_status st;if(!m || m->executing) return ES_EINVAL; + st=esk_esp32_matrix_gpio(&m->matrix,cpu,nmi,asserted);if(st==ES_OK) sync_inputs(m);return st; +} +es_status esk_esp32_machine_nmi_mask(esk_esp32_machine *m,unsigned cpu,int asserted) +{ + es_status st;if(!m || m->executing) return ES_EINVAL; + st=esk_esp32_matrix_nmi_mask(&m->matrix,cpu,asserted);if(st==ES_OK) sync_inputs(m);return st; +} es_status esk_esp32_machine_create(esk_alloc *a,esk_esp32_machine **out) { esk_esp32_machine *m;es_status st;unsigned i; @@ -14,7 +79,8 @@ es_status esk_esp32_machine_create(esk_alloc *a,esk_esp32_machine **out) m=(esk_esp32_machine *)esk_calloc(a,1,sizeof *m);if(!m) return ES_ENOMEM; m->a=a;st=esk_esp32_fabric_create(a,&m->fabric); if(st!=ES_OK) { esk_free(a,m,sizeof *m);return st; } - for(i=0;i<2;i++) esk_xtensa_windows_init(&m->cpu[i],0,NULL); + esk_esp32_matrix_init(&m->matrix); + for(i=0;i<2;i++) { esk_xtensa_windows_init(&m->cpu[i],0,NULL);m->view[i].m=m;m->view[i].cpu=i; } *out=m;return ES_OK; } void esk_esp32_machine_destroy(esk_esp32_machine *m) @@ -41,8 +107,9 @@ es_status esk_esp32_machine_step(esk_esp32_machine *m,unsigned cpu,unsigned *eve if(fault) memset(fault,0,sizeof *fault); if(!m || cpu>1 || !event) return ES_EINVAL; if(!m->running[cpu]) { *event=19;return ES_OK; } - bus=esk_esp32_fabric_bus(m->fabric,cpu); + sync_inputs(m);bus=esk_esp32_machine_bus(m,cpu);m->executing=1; st=esk_xtensa_window_step(&m->cpu[cpu],&bus,event,fault); + m->executing=0;sync_inputs(m); if(st!=ES_OK) m->running[cpu]=0; else if(!*event) m->retired[cpu]++; return st; diff --git a/core/mcu/esp32/machine.h b/core/mcu/esp32/machine.h index 53135f7d0..30c57cfd2 100644 --- a/core/mcu/esp32/machine.h +++ b/core/mcu/esp32/machine.h @@ -3,16 +3,27 @@ #ifndef ESK_ESP32_MACHINE_H #define ESK_ESP32_MACHINE_H #include "mcu/esp32/fabric.h" +#include "mcu/esp32/matrix.h" #include "mcu/xtensa/windows.h" typedef struct esk_esp32_machine esk_esp32_machine; /* Experimental two-context execution harness over shared internal memory. * Deterministic serialized instruction turns, not a cycle scheduler or full SoC. - * No DPORT/reset/stall registers, boot ROM, peripherals, interrupt matrix, + * No DPORT core-reset/stall registers, boot ROM, peripheral generators, * cache arbitration/coherence or board model. Host controls are debug/test * operations, not emulated hardware control registers. No concurrent callers. */ es_status esk_esp32_machine_create(esk_alloc *a,esk_esp32_machine **out); void esk_esp32_machine_destroy(esk_esp32_machine *m); esk_esp32_fabric *esk_esp32_machine_fabric(esk_esp32_machine *m); +/* Unified data bus includes interrupt-matrix MMIO; fetch uses instruction + * memory and CAS remains SRAM-only. + * Direct host bus calls are permitted only between turns. Matrix peripheral + * inputs belong exclusively to the machine: use source/GPIO inputs below, + * not irq_line on CPU peripheral input numbers. Internal timer/software and + * profiling inputs remain CPU-local. No API is re-entrant or thread-safe. */ +esk_xtensa_memory esk_esp32_machine_bus(esk_esp32_machine *m,unsigned cpu); +es_status esk_esp32_machine_source(esk_esp32_machine *m,unsigned source,int asserted); +es_status esk_esp32_machine_gpio_irq(esk_esp32_machine *m,unsigned cpu,int nmi,int asserted); +es_status esk_esp32_machine_nmi_mask(esk_esp32_machine *m,unsigned cpu,int asserted); /* Clear one architecture profile, pause it and set explicit entry. Shared * memory and per-core remap survive. This is not a silicon reset sequence. */ es_status esk_esp32_machine_configure(esk_esp32_machine *m,unsigned cpu,uint32_t entry); diff --git a/core/mcu/esp32/matrix.c b/core/mcu/esp32/matrix.c new file mode 100644 index 000000000..e6f2aace3 --- /dev/null +++ b/core/mcu/esp32/matrix.c @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* Register facts: ESP32 TRM v5.9 and ESP-IDF v4.4.7 soc/dport_reg.h. + * Original implementation; no vendor controller code linked. */ +#include "mcu/esp32/matrix.h" +#include +#define INTERNAL UINT32_C(0x200188c0) /* inputs 6,7,11,15,16,29 */ +static int valid_source(unsigned source) { return source<69 && source!=41 && source!=42; } +void esk_esp32_matrix_init(esk_esp32_matrix *m) +{ + unsigned cpu,source; + if(!m) return; + memset(m,0,sizeof *m); + for(cpu=0;cpu<2;cpu++) for(source=0;source<69;source++) m->map[cpu][source]=16; +} +es_status esk_esp32_matrix_source(esk_esp32_matrix *m,unsigned source,int asserted) +{ + uint32_t bit; + if(!m || !valid_source(source) || (source>=22 && source<=27)) return ES_EINVAL; + bit=UINT32_C(1)<<(source%32); + if(asserted) m->sources[source/32]|=bit;else m->sources[source/32]&=~bit; + return ES_OK; +} +es_status esk_esp32_matrix_gpio(esk_esp32_matrix *m,unsigned cpu,int nmi,int asserted) +{ + uint32_t bit=nmi ? 2u : 1u; + if(!m || cpu>1) return ES_EINVAL; + if(asserted) m->gpio[cpu]|=bit;else m->gpio[cpu]&=~bit; + return ES_OK; +} +es_status esk_esp32_matrix_nmi_mask(esk_esp32_matrix *m,unsigned cpu,int asserted) +{ + if(!m || cpu>1) return ES_EINVAL; + m->nmi_mask[cpu]=asserted ? 1u : 0u;return ES_OK; +} +uint32_t esk_esp32_matrix_status(const esk_esp32_matrix *m,unsigned cpu,unsigned group) +{ + uint32_t result;unsigned i; + if(!m || cpu>1 || group>2) return 0; + result=m->sources[group]; + if(!group) { + result|=m->gpio[cpu]<<22; + for(i=0;i<4;i++) result|=m->ipi[i]<<(24+i); + } + return result; +} +uint32_t esk_esp32_matrix_levels(const esk_esp32_matrix *m,unsigned cpu) +{ + uint32_t result=0,status[3];unsigned i; + if(!m || cpu>1) return 0; + for(i=0;i<3;i++) status[i]=esk_esp32_matrix_status(m,cpu,i); + for(i=0;i<69;i++) if(valid_source(i) && (status[i/32]&(UINT32_C(1)<<(i%32)))) { + uint32_t bit=UINT32_C(1)<<(m->map[cpu][i]&31u); + if(!(bit&INTERNAL)) result|=bit; + } + if(m->nmi_mask[cpu]) result&=~UINT32_C(0x4000); + return result; +} +/* Return kind 1=IPC, 2=status, 3=map. Reserved map slots remain holes. */ +static es_status decode(uint32_t address,unsigned bytes,unsigned *kind,unsigned *cpu,unsigned *index) +{ + uint32_t offset; + if(address<0x3ff000dc || address>0x3ff0032b) return ES_EUNSUPPORTED; + if(bytes!=4 || (address&3u)) return ES_EINVAL; + offset=address-0x3ff00000; + if(offset<0xec) { *kind=1;*index=(offset-0xdc)/4;*cpu=0; } + else if(offset<0x104) { *kind=2;*cpu=(offset-0xec)/12;*index=((offset-0xec)%12)/4; } + else { + *kind=3;*cpu=offset<0x218 ? 0u : 1u; + *index=(offset-(*cpu ? 0x218u : 0x104u))/4; + if(!valid_source(*index)) return ES_EUNSUPPORTED; + } + return ES_OK; +} +es_status esk_esp32_matrix_read(const esk_esp32_matrix *m,uint32_t address,unsigned bytes,uint32_t *value) +{ + unsigned kind,cpu,index;es_status st; + if(!m || !value) return ES_EINVAL; + st=decode(address,bytes,&kind,&cpu,&index);if(st!=ES_OK) return st; + *value=kind==1 ? m->ipi[index] : kind==2 ? esk_esp32_matrix_status(m,cpu,index) : m->map[cpu][index];return ES_OK; +} +es_status esk_esp32_matrix_write(esk_esp32_matrix *m,uint32_t address,unsigned bytes,uint32_t value) +{ + unsigned kind,cpu,index;es_status st; + if(!m) return ES_EINVAL; + st=decode(address,bytes,&kind,&cpu,&index);if(st!=ES_OK) return st; + if(kind==1) m->ipi[index]=value&1u; + else if(kind==3) m->map[cpu][index]=value&31u; + return ES_OK; +} diff --git a/core/mcu/esp32/matrix.h b/core/mcu/esp32/matrix.h new file mode 100644 index 000000000..739d5bf45 --- /dev/null +++ b/core/mcu/esp32/matrix.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_ESP32_MATRIX_H +#define ESK_ESP32_MATRIX_H +#include "embsim/types.h" +/* Original ESP32 interrupt matrix, TRM v5.9 ch.8/12. The 69 source slots + * include reserved 41/42, which are rejected. GPIO slots 22/23 are core-local; + * all other implemented sources are shared, including four software sources. + * Peripheral generators and PID hardware are absent: external levels and PID + * NMI-mask inputs must be supplied explicitly. No APB latency, pulse-width, + * reset-domain or electrical timing qualification. Serialized calls only. */ +typedef struct esk_esp32_matrix { + uint32_t map[2][69],sources[3],gpio[2],ipi[4],nmi_mask[2]; +} esk_esp32_matrix; +/* Documented map-register reset field=16 (disconnected); sources/IPC low. */ +void esk_esp32_matrix_init(esk_esp32_matrix *m); +/* Shared peripheral input; reject GPIO, software-owned and reserved slots. */ +es_status esk_esp32_matrix_source(esk_esp32_matrix *m,unsigned source,int asserted); +es_status esk_esp32_matrix_gpio(esk_esp32_matrix *m,unsigned cpu,int nmi,int asserted); +es_status esk_esp32_matrix_nmi_mask(esk_esp32_matrix *m,unsigned cpu,int asserted); +/* Raw source status is independent of mapping/masking. */ +uint32_t esk_esp32_matrix_status(const esk_esp32_matrix *m,unsigned cpu,unsigned group); +/* OR-combined CPU peripheral-input levels. Internal CPU inputs never driven. */ +uint32_t esk_esp32_matrix_levels(const esk_esp32_matrix *m,unsigned cpu); +/* DPORT 0x3ff000dc..0x3ff00328: IPC, status and defined map registers only. + * Qualified profile requires aligned 32-bit data accesses. RO status writes + * are ignored; reserved bits read zero and are ignored on writes. Unknown + * addresses/holes remain unsupported. No instruction fetch or MMIO CAS. */ +es_status esk_esp32_matrix_read(const esk_esp32_matrix *m,uint32_t address,unsigned bytes,uint32_t *value); +es_status esk_esp32_matrix_write(esk_esp32_matrix *m,uint32_t address,unsigned bytes,uint32_t value); +#endif diff --git a/docs/STATUS.md b/docs/STATUS.md index eb1c97209..7c2d8e177 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -42,7 +42,8 @@ RFI and WAITI suspension/wake (11,571 native assertions). CCOUNT/CCOMPARE0..2 use explicit supplied ticks, including WAITI wake and wraparound (81 assertions). Referees pass 2,048 vendor-configuration interrupt comparisons and 8,192 clock/deadline comparisons against 263,719 individual ticks. The SoC interrupt -matrix, peripheral routing, scheduler timing, SMP and board boot remain absent. +matrix is now covered by a separate functional implementation below; peripheral +generators, scheduler timing, full SMP and board boot remain absent. Conditional moves/min/max/high products/division/normalization and bit branches now pass 128 additional native assertions. SCOMPARE1/S32C1I require one indivisible bus callback, with no non-atomic fallback (67 assertions). Another 832 actual @@ -54,11 +55,21 @@ two-context harness shares this RAM while keeping CPU/interrupt/clock state private. Native fixtures cover address/permission/remap/fault/wait boundaries; 512 actual ESP32 compiled task executions and 128 replay comparisons pass through the native bus (229,322 instructions). This remains a bounded memory/execution -harness: cache/MMU, DPORT hardware controls, complete SMP and SoC boot are absent. +harness: cache/MMU, DPORT core reset/stall controls, complete SMP and SoC boot are absent. L32AI/S32RI now provide acquire/release ordering under the synchronous completed callback contract (30,732 native assertions). The compiled shared-bus acceptance includes acquire/CAS/release spinlocks protecting ordinary writes. Asynchronous transactions, cache coherence and physical timing remain unsupported. +The interrupt matrix now exposes per-core DPORT maps/raw status, four shared +software-generated interrupts, shared OR routes, disconnected internal-input +selectors, core-local GPIO and explicit PID NMI-mask inputs. Firmware can wake a +peer from WAITI, acknowledge its source and return. Delivery synchronizes after +instruction commit, including paused-peer edges. New fixtures pass 64,374 matrix +and 101 integration assertions; pinned-header referees pass 4,288 routes and 512 +combined-source/mask cases. Actual ESP32 C sender/assembled ISR acceptance passes +128 mailbox executions, 2,112 interrupts and 64 exact replays (188,480 retired +instructions). Peripheral generators, PID hardware, DPORT reset/stall, APB timing, +flash/cache and boot remain absent; no complete ISR/RTOS or timing claim follows. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 8b3b20365..38c05c604 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -1,6 +1,6 @@ # Original ESP32 dual-core target: identity and implementation gates -**Status: firmware inspection, experimental Xtensa execution and a two-context internal-RAM harness implemented; no runnable board target is added.** Requested by +**Status: firmware inspection, experimental Xtensa execution, shared internal RAM and interrupt routing implemented; no runnable board target is added.** Requested by the owner on 2026-11-04. This is the original ESP32 family, not ESP32-C3, ESP32-S2 or ESP32-S3. The generic marketplace title “ESP-32 / ESP-32S WiFi+Bluetooth Dual Cores” does not identify a reproducible board revision. @@ -370,10 +370,11 @@ python tests/vendor/xtensa_integer/run_timer_clock.py \ --core-isa /path/to/xtensa-esp32-elf/include/xtensa/config/core-isa.h ``` -This is not the SoC interrupt matrix, peripheral source routing, cross-core -interrupt generation, full ISR/RTOS context switching, debug/NMI cycle timing or -board power behavior. No executable board target is registered. Those remain -separate qualification gates rather than being inferred from CPU input tests. +These CPU input tests alone do not establish SoC routing or cross-core +interrupt generation. The separate matrix implementation below covers those +functional paths. Full ISR/RTOS context switching, debug/NMI cycle timing and +board power behavior remain separate qualification gates. No runnable board +target is registered. ## Implemented: extended integer operations, bit branches and conditional store @@ -537,6 +538,92 @@ python tests/vendor/xtensa_integer/run_machine_c.py \ --nm /path/to/xtensa-esp32-elf-nm ``` +## Implemented: interrupt matrix and firmware-generated cross-core interrupts + +`core/mcu/esp32/matrix.c` implements the register-routing contract from TRM v5.9 +chapters 8 and 12. Each core has 69 numbered source slots; slots 41/42 are +reserved and rejected. GPIO slots 22/23 have separate per-core input levels; +other defined sources are shared. Peripheral generators are not implemented by +this controller: external source levels must be supplied explicitly. + +| Register group | Address range (inclusive, 32-bit registers) | Behavior | +| --- | --- | --- | +| Software-generated sources 0..3 | `3FF000DC–3FF000E8` | Bit 0 asserts/clears shared sources 24..27 | +| PRO source status 0..2 | `3FF000EC–3FF000F4` | Raw source levels, independent of routing and masks | +| APP source status 0..2 | `3FF000F8–3FF00100` | Same shared sources, core-local GPIO slots | +| PRO source maps | `3FF00104–3FF00214` | Five-bit CPU input selector, except reserved slots | +| APP source maps | `3FF00218–3FF00328` | Independent selector for the other core | + +Map fields initialize to documented reset value 16, disconnecting the sources. +Inputs 6, 7, 11, 15, 16 and 29 are CPU-internal: mapping a peripheral to any of +these disables that route without driving or clearing the internal input. +Other destinations OR all active mapped sources. Clearing one shared source +cannot deassert an input while another mapped source remains asserted. Active +sources can be remapped. Only defined field bits are stored/read; status writes +are ignored. Reserved map holes and unrelated DPORT registers remain unsupported. +The qualified bus profile requires aligned 32-bit accesses and rejects narrow +or misaligned transactions; MMIO instruction fetch and CAS are unsupported. + +The two-context machine now exposes a unified data bus with these registers. +Matrix changes from firmware writes reach CPU inputs only after instruction +commit, avoiding mutation of a copied executing CPU state. Inputs are synchronized +before each runnable turn and after each instruction, for both profiles, including +a debug-paused peer. Separate high/low firmware writes therefore latch an edge +even when the peer has no intervening execution turn. Host bus/input calls are +allowed only between turns and synchronize immediately. Direct CPU `irq_line` +calls for peripheral input numbers conflict with this ownership; use the machine's +source/GPIO/NMI-mask APIs. Internal clock/software/profiling inputs remain +CPU-local. This is a serialized functional model, not host-thread-safe routing. + +PID NMI-mask inputs are explicitly supplied per core and suppress only the +matrix NMI output; raw status remains readable. The PID controller itself, +source pulse shaping/minimum widths, APB/arbitration latency, hardware reset and +power domains, other DPORT registers, peripheral source generators, flash/cache, +SoC boot and real-hardware timing remain unqualified or absent. No vector address +or peripheral behavior is invented from a source number. No board target is +registered by this change. + +Proof: + +- `test_esp32_matrix`: 64,374 assertions across every defined source/destination, + reset fields, core isolation, GPIO-specific status, shared OR, remapping, + NMI masks, software-source fields, RO status and invalid accesses. +- `test_esp32_ipi`: 101 assertions for a fetched sender, peer WAITI wake, + handler acknowledgement/RFI, paused-peer edge capture, same-core MMIO changes + surviving instruction commit, shared edge-input OR and internal-input isolation. +- `run_matrix.py`: 4,288 independent vendor-addressed routes and 512 combined + source/map/mask comparisons. It parses the hash-pinned vendor headers below + for register addresses/defaults/source slots and CPU-internal input types. +- `run_ipi_c.py`: actual ESP32-compiled windowed C sender plus assembled ISR, + 128 mailbox runs, 2,112 routed interrupts, 64 exact instruction/event replays + and 188,480 retired instructions. The ISR saves/restores integer registers on + an EXCSAVE2 stack, uses EXCSAVE3 for the interrupted SP, clears the source and + publishes a release acknowledgement. Checks cover all messages, source clear, + stack guards, general registers, ABI return and window state. This is not a + complete production ISR, nested exception or RTOS context-switch proof. +- The native shared-RAM atomic/spinlock acceptance still passes unchanged. + +External reference headers (ESP-IDF v4.4.7, used as facts, not linked code): + +- [soc/dport_reg.h](https://github.com/espressif/esp-idf/blob/v4.4.7/components/soc/esp32/include/soc/dport_reg.h), + SHA-256 `38deaa4ef02016ee9cdf490a9bfe4b587f11d1d1e6a2329dc858a50b9b248817`. +- [soc/soc.h](https://github.com/espressif/esp-idf/blob/v4.4.7/components/soc/esp32/include/soc/soc.h), + SHA-256 `7a7148c288d7c289c961810e5617fa7b14d105b28349a45f772150b1e0845dd3`. +- The hash-pinned actual ESP32 `core-isa.h` described above supplies CPU input + types. No vendor controller implementation or ROM is embedded. + +```sh +python tests/vendor/xtensa_integer/run_matrix.py \ + --dport-reg /path/to/esp-idf/components/soc/esp32/include/soc/dport_reg.h \ + --soc-header /path/to/esp-idf/components/soc/esp32/include/soc/soc.h \ + --core-isa /path/to/xtensa-esp32-elf/include/xtensa/config/core-isa.h +python tests/vendor/xtensa_integer/run_ipi_c.py \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld \ + --nm /path/to/xtensa-esp32-elf-nm +``` + ## Implemented: standalone firmware integrity inspection `embsim image-info esp32 app.bin` inspects a plaintext original-ESP32 image diff --git a/tests/unit/test_esp32_ipi.c b/tests/unit/test_esp32_ipi.c new file mode 100644 index 000000000..37366ccc6 --- /dev/null +++ b/tests/unit/test_esp32_ipi.c @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/machine.h" +static const uint32_t general[3]={0x40081000,0x40081040,0x40081080}; +static const uint32_t high[6]={0x40082000,0x40082040,0x40082080,0x400820c0,0x40082100,0x40082140}; +static void profile(esk_esp32_machine *m,unsigned cpu,uint32_t entry) +{ + esk_xtensa_windows *w; + CHECK_EQ(esk_esp32_machine_configure(m,cpu,entry),ES_OK); + w=esk_esp32_machine_core(m,cpu);esk_xtensa_exception_configure(w,general); + CHECK_EQ(esk_xtensa_irq_configure(w,high),ES_OK); +} +static void firmware_signal_and_acknowledge(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_xtensa_memory bus;esk_xtensa_windows *pro,*app; + unsigned event;esk_xtensa_fault fault;uint32_t value=0; + unsigned char sender[]={0x22,0x63,0x00,0x3d,0xf0}; /* S32I A2,A3,0; NOP.N */ + unsigned char receiver[]={0x00,0x70,0x00,0x3d,0xf0}; /* WAITI 0; NOP.N */ + unsigned char handler[]={0x22,0x63,0x00,0x10,0x32,0x00}; /* S32I A2,A3,0; RFI 2 */ + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK);bus=esk_esp32_machine_bus(m,0); + CHECK_EQ(esk_esp32_fabric_load(esk_esp32_machine_fabric(m),0,0x40080000,sender,sizeof sender),ES_OK); + CHECK_EQ(esk_esp32_fabric_load(esk_esp32_machine_fabric(m),0,0x40080010,receiver,sizeof receiver),ES_OK); + CHECK_EQ(esk_esp32_fabric_load(esk_esp32_machine_fabric(m),1,high[0],handler,sizeof handler),ES_OK); + profile(m,0,0x40080000);profile(m,1,0x40080010);pro=esk_esp32_machine_core(m,0);app=esk_esp32_machine_core(m,1); + pro->cpu.a[2]=1;pro->cpu.a[3]=0x3ff000dc;app->cpu.a[2]=0;app->cpu.a[3]=0x3ff000dc; + app->irq.intenable=1u<<19; + CHECK_EQ(bus.write(bus.ctx,0x3ff00278,4,19),ES_OK); + CHECK_EQ(esk_esp32_machine_run_core(m,0,1),ES_OK);CHECK_EQ(esk_esp32_machine_run_core(m,1,1),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(app->irq.waiting,1); + CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK);CHECK_EQ(event,0); + CHECK_EQ(pro->cpu.pc,0x40080003);CHECK_EQ(esk_xtensa_irq_pending(pro),0); + CHECK_EQ(esk_xtensa_irq_pending(app),1u<<19); + CHECK_EQ(bus.read(bus.ctx,0x3ff000ec,4,&value),ES_OK);CHECK_EQ(value,1u<<24); + CHECK_EQ(bus.read(bus.ctx,0x3ff000f8,4,&value),ES_OK);CHECK_EQ(value,1u<<24); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,12); + CHECK_EQ(app->cpu.pc,high[0]);CHECK_EQ(app->irq.epc[2],0x40080013);CHECK_EQ(app->irq.waiting,0); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,0); + CHECK_EQ(esk_xtensa_irq_pending(app),0); + CHECK_EQ(bus.read(bus.ctx,0x3ff000dc,4,&value),ES_OK);CHECK_EQ(value,0); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(app->cpu.pc,0x40080013); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(app->cpu.pc,0x40080015); + CHECK_EQ(esk_esp32_machine_retired(m,0),1);CHECK_EQ(esk_esp32_machine_retired(m,1),4); + CHECK_EQ(bus.fetch(bus.ctx,0x3ff000dc,1,&value),ES_EUNSUPPORTED); + CHECK_EQ(bus.compare_exchange(bus.ctx,0x3ff000dc,0,1,&value),ES_EUNSUPPORTED); + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +static void paused_edges_and_shared_levels(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_xtensa_memory bus;esk_xtensa_windows *pro,*app;unsigned event;esk_xtensa_fault fault; + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK);bus=esk_esp32_machine_bus(m,0); + profile(m,0,0x40080000);profile(m,1,0x40080000);pro=esk_esp32_machine_core(m,0);app=esk_esp32_machine_core(m,1); + CHECK_EQ(bus.write(bus.ctx,0x3ff00278,4,10),ES_OK);app->irq.intenable=1u<<10; + /* Host writes between turns, while APP is debug-paused: edge survives low. */ + CHECK_EQ(bus.write(bus.ctx,0x3ff000dc,4,1),ES_OK);CHECK_EQ(bus.write(bus.ctx,0x3ff000dc,4,0),ES_OK); + CHECK_EQ(app->irq.lines&(1u<<10),0);CHECK_EQ(esk_xtensa_irq_pending(app),1u<<10); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,19); + CHECK_EQ(esk_esp32_machine_run_core(m,1,1),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,11); + CHECK_EQ(esk_xtensa_irq_write(app,227,1u<<10),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(app),0); + /* Matrix cannot drive or clear an internal compare input. */ + CHECK_EQ(esk_xtensa_irq_line(pro,6,1),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff0018c,4,19),ES_OK);CHECK_EQ(bus.write(bus.ctx,0x3ff00190,4,19),ES_OK); + CHECK_EQ(esk_esp32_machine_source(m,34,1),ES_OK);CHECK_EQ(esk_esp32_machine_source(m,35,1),ES_OK); + CHECK_EQ(esk_xtensa_irq_pending(pro),(1u<<19)|(1u<<6)); + CHECK_EQ(esk_esp32_machine_source(m,34,0),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(pro),(1u<<19)|(1u<<6)); + CHECK_EQ(esk_esp32_machine_source(m,35,0),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(pro),1u<<6); + /* Core-specific GPIO and NMI-mask inputs do not affect the peer. */ + CHECK_EQ(bus.write(bus.ctx,0x3ff0015c,4,14),ES_OK); + CHECK_EQ(esk_esp32_machine_nmi_mask(m,0,1),ES_OK); + CHECK_EQ(esk_esp32_machine_gpio_irq(m,0,0,1),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(pro),1u<<6); + CHECK_EQ(esk_esp32_machine_nmi_mask(m,0,0),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(pro),(1u<<14)|(1u<<6)); + CHECK_EQ(esk_xtensa_irq_pending(app),0); + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +static void self_signal_and_edge_or(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_xtensa_memory bus;esk_xtensa_windows *pro; + unsigned event;esk_xtensa_fault fault; + unsigned char stores[]={0x22,0x63,0x00,0x22,0x63,0x00}; + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK);bus=esk_esp32_machine_bus(m,0); + CHECK_EQ(esk_esp32_fabric_load(esk_esp32_machine_fabric(m),0,0x40080000,stores,sizeof stores),ES_OK); + profile(m,0,0x40080000);pro=esk_esp32_machine_core(m,0); + CHECK_EQ(bus.write(bus.ctx,0x3ff00164,4,10),ES_OK);CHECK_EQ(bus.write(bus.ctx,0x3ff00168,4,10),ES_OK); + pro->cpu.a[2]=1;pro->cpu.a[3]=0x3ff000dc; + CHECK_EQ(esk_esp32_machine_run_core(m,0,1),ES_OK); + /* IRQ changes from our own MMIO store must survive instruction commit. */ + CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK);CHECK_EQ(event,0); + CHECK_EQ(pro->cpu.pc,0x40080003);CHECK_EQ(esk_xtensa_irq_pending(pro),1u<<10); + CHECK_EQ(esk_xtensa_irq_write(pro,227,1u<<10),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(pro),0); + CHECK_EQ(bus.write(bus.ctx,0x3ff000e0,4,1),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(pro),0); + pro->cpu.a[2]=0; + CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK);CHECK_EQ(pro->cpu.pc,0x40080006); + CHECK_EQ(pro->irq.lines,1u<<10);CHECK_EQ(esk_xtensa_irq_pending(pro),0); + CHECK_EQ(bus.write(bus.ctx,0x3ff000e0,4,0),ES_OK);CHECK_EQ(pro->irq.lines,0); + CHECK_EQ(bus.write(bus.ctx,0x3ff000dc,4,1),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(pro),1u<<10); + CHECK_EQ(esk_esp32_machine_retired(m,0),2); + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +TEST_MAIN_BEGIN + (void)t_str_eq;RUN(firmware_signal_and_acknowledge);RUN(paused_edges_and_shared_levels);RUN(self_signal_and_edge_or); +TEST_MAIN_END diff --git a/tests/unit/test_esp32_matrix.c b/tests/unit/test_esp32_matrix.c new file mode 100644 index 000000000..3bbedc988 --- /dev/null +++ b/tests/unit/test_esp32_matrix.c @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/matrix.h" +static uint32_t map_address(unsigned cpu,unsigned source) { return 0x3ff00104u+cpu*0x114u+source*4u; } +static void drive(esk_esp32_matrix *m,unsigned cpu,unsigned source,int asserted) +{ + if(source==22 || source==23) CHECK_EQ(esk_esp32_matrix_gpio(m,cpu,source==23,asserted),ES_OK); + else if(source>=24 && source<=27) CHECK_EQ(esk_esp32_matrix_write(m,0x3ff000dcu+(source-24)*4u,4,asserted ? 1u : 0u),ES_OK); + else CHECK_EQ(esk_esp32_matrix_source(m,source,asserted),ES_OK); +} +static void all_routes(void) +{ + unsigned cpu,source,input;const uint32_t internal=UINT32_C(0x200188c0); + for(cpu=0;cpu<2;cpu++) for(source=0;source<69;source++) if(source!=41 && source!=42) for(input=0;input<32;input++) { + esk_esp32_matrix m;uint32_t value=0,bit=UINT32_C(1)< Date: Thu, 5 Nov 2026 02:32:28 +0000 Subject: [PATCH 15/57] Qualify bidirectional ESP32 mailboxes with compiled C interrupt handlers --- CHANGELOG.md | 4 +- docs/STATUS.md | 7 ++- docs/engineering/esp32-dual-core.md | 10 ++-- tests/vendor/README.md | 6 +- tests/vendor/xtensa_integer/run_ipi_c.py | 76 ++++++++++++++---------- 5 files changed, 62 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 035c6a20d..c54cecced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,8 @@ versions follow semantic versioning. There is no release yet. paused-peer edges and same-core MMIO IRQ updates survive correctly. No physical APB timing, DPORT reset/stall, cache/flash, full SMP or boot claim is added. - Added 64,374 matrix/101 integration assertions and pinned-header referees for - 4,288 routes/512 combined-source cases. Actual ESP32 C sender/assembled ISR - passes 128 mailbox runs, 2,112 interrupts and 64 exact replays (188,480 retired + 4,288 routes/512 combined-source cases. Actual ESP32 C sender/C handler with + an assembled entry/return wrapper passes 256 mailbox runs, 4,224 interrupts and 128 exact replays (512,128 retired instructions), preserving integer registers and stack guards. ### Experimental Xtensa acquire/release and shared spinlocks diff --git a/docs/STATUS.md b/docs/STATUS.md index 7c2d8e177..e52356b07 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -66,9 +66,10 @@ selectors, core-local GPIO and explicit PID NMI-mask inputs. Firmware can wake a peer from WAITI, acknowledge its source and return. Delivery synchronizes after instruction commit, including paused-peer edges. New fixtures pass 64,374 matrix and 101 integration assertions; pinned-header referees pass 4,288 routes and 512 -combined-source/mask cases. Actual ESP32 C sender/assembled ISR acceptance passes -128 mailbox executions, 2,112 interrupts and 64 exact replays (188,480 retired -instructions). Peripheral generators, PID hardware, DPORT reset/stall, APB timing, +combined-source/mask cases. Actual ESP32 C sender/C handler acceptance with an +assembled entry/return wrapper passes 256 mailbox executions, 4,224 interrupts and 128 exact replays (512,128 retired +instructions), in both directions. Peripheral generators, PID hardware, DPORT +reset/stall, APB timing, flash/cache and boot remain absent; no complete ISR/RTOS or timing claim follows. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 38c05c604..0ed1f281d 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -594,10 +594,12 @@ Proof: - `run_matrix.py`: 4,288 independent vendor-addressed routes and 512 combined source/map/mask comparisons. It parses the hash-pinned vendor headers below for register addresses/defaults/source slots and CPU-internal input types. -- `run_ipi_c.py`: actual ESP32-compiled windowed C sender plus assembled ISR, - 128 mailbox runs, 2,112 routed interrupts, 64 exact instruction/event replays - and 188,480 retired instructions. The ISR saves/restores integer registers on - an EXCSAVE2 stack, uses EXCSAVE3 for the interrupted SP, clears the source and +- `run_ipi_c.py`: actual ESP32-compiled windowed C sender and C handler plus assembled + entry/return wrapper, + 256 mailbox runs, 4,224 routed interrupts, 128 exact instruction/event replays + and 512,128 retired instructions, in both directions. The ISR saves/restores + integer registers on an EXCSAVE2 stack, uses EXCSAVE3 for the interrupted SP and calls the C handler + with EXCM cleared and interrupt level 2 retained. The handler clears the source and publishes a release acknowledgement. Checks cover all messages, source clear, stack guards, general registers, ABI return and window state. This is not a complete production ISR, nested exception or RTOS context-switch proof. diff --git a/tests/vendor/README.md b/tests/vendor/README.md index 2575740e6..fb763848d 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -96,7 +96,9 @@ contract linked above. `xtensa_integer/run_ipi_c.py` takes the actual ESP32 `--compiler`, `--objcopy`, `--linker` and `--nm`. A compiled C sender uses DPORT MMIO to wake a WAITI peer; an assembled context-saving ISR acknowledges its source and publishes a release -ACK. No Python memory/interrupt callbacks: 128 mailbox runs, 2,112 interrupts, -64 exact replays, 188,480 retired instructions. Checks messages, source clear, +ACK through an actual ESP32-compiled C handler (windowed ABI). Both PRO-to-APP +and APP-to-PRO directions pass. No Python memory/interrupt callbacks: +256 mailbox runs, 4,224 interrupts, +128 exact replays, 512,128 retired instructions. Checks messages, source clear, stack guards, general registers and ABI/window return. Not full ISR/RTOS, PID, peripheral generators, APB timing or SoC boot qualification. diff --git a/tests/vendor/xtensa_integer/run_ipi_c.py b/tests/vendor/xtensa_integer/run_ipi_c.py index d0f0aeb5f..e3126d674 100644 --- a/tests/vendor/xtensa_integer/run_ipi_c.py +++ b/tests/vendor/xtensa_integer/run_ipi_c.py @@ -4,7 +4,7 @@ """ESP32-compiled cross-core mailbox with native DPORT routing and ISR return. A windowed-ABI sender signals a WAITI peer. An assembled context-saving entry -clears the source and publishes a release ACK from an assembled ISR. Neither +calls a windowed-ABI C handler that clears the source and publishes a release ACK. Neither memory nor interrupt delivery uses Python callbacks. This is not ESP-IDF boot, full ISR/RTOS context switching, reset hardware or cycle qualification. """ @@ -30,6 +30,17 @@ } ''' +HANDLER = ''' +void irq_body(void) { + volatile unsigned *mailbox=(volatile unsigned *)0x3ffb0000; + volatile unsigned *ipi=(volatile unsigned *)0x3ff000dc; + unsigned request=mailbox[0]; + *ipi=0; + mailbox[2]=mailbox[2]+1; + __atomic_store_n(mailbox+1,request,__ATOMIC_RELEASE); +} +''' + def main(): ap = argparse.ArgumentParser(description=__doc__) @@ -38,19 +49,20 @@ def main(): ap.add_argument('--cc', default='cc'); args = ap.parse_args() with tempfile.TemporaryDirectory(prefix='embsim-ipi-c-') as tmp: out = Path(tmp); objects = [] - for name, source in [('sender', SENDER)]: + for name, source in [('sender', SENDER), ('handler', HANDLER)]: src, obj = out / (name + '.c'), out / (name + '.o'); objects.append(obj) src.write_text(source, encoding='utf-8') subprocess.run([args.compiler, '-B' + str(Path(args.compiler).parent) + '/', '-O1', '-mtext-section-literals', '-ffreestanding', '-fno-builtin', '-c', str(src), '-o', str(obj)], check=True) # Fixture IRQ prologue/epilogue saves the interrupted integer context # on an EXCSAVE2 stack, using EXCSAVE3 to save the idle SP. Not an RTOS handler. - entry = ['.literal .Lmailbox,0x3ffb0000', '.literal .Lipi,0x3ff000dc', '.text', '.align 4', + entry = ['.text', '.align 4', '.global irq_entry', 'irq_entry:', 'wsr a1,EXCSAVE3', 'rsr a1,EXCSAVE2', 'addi a1,a1,-64'] regs = [0] + list(range(2, 16)) entry += [f's32i a{r},a1,{i * 4}' for i, r in enumerate(regs)] - entry += ['l32r a2,.Lmailbox', 'l32i a3,a2,0', 'l32r a4,.Lipi', 'movi a5,0', 's32i a5,a4,0', - 'l32i a4,a2,8', 'addi a4,a4,1', 's32i a4,a2,8', 's32ri a3,a2,4'] + # ENTRY/RETW require EXCM clear. Keep INTLEVEL=2 throughout the C + # call, so our level-2 source cannot nest itself. RFI restores EPS2. + entry += ['rsr a2,PS', 'movi a3,-17', 'and a2,a2,a3', 'wsr a2,PS', 'rsync', 'call4 irq_body'] entry += [f'l32i a{r},a1,{i * 4}' for i, r in enumerate(regs)] entry += ['rsr a1,EXCSAVE3', 'rfi 2', '.align 4', '.global idle', 'idle:', 'waiti 0', 'j idle'] @@ -86,7 +98,8 @@ def bind(name, result, *params): exceptions = bind('esk_xtensa_exception_configure', None, wp, C.POINTER(C.c_uint32)) irq = bind('esk_xtensa_irq_configure', C.c_int, wp, C.POINTER(C.c_uint32)) total = 0; deliveries = 0; runs = 0; code = binary.read_bytes() - for trial in range(64): + for trial in range(128): + sender_cpu = trial // 64; receiver_cpu = 1 - sender_cpu count = 1 + trial % 32; seed = 0x10203040 + trial * 256; replay_trace = None for replay in range(2): allocator = Allocator(); alloc_init(C.byref(allocator), None) @@ -94,55 +107,58 @@ def bind(name, result, *params): try: ram = fabric(machine); bus = bus_get(machine, 0) assert load(ram, 0, 0x40080000, C.c_char_p(code), len(code)) == 0 - assert configure(machine, 0, symbols['sender']) == 0 - assert configure(machine, 1, symbols['idle']) == 0 - pro, app = [core(machine, cpu).contents for cpu in range(2)] - pro.ps |= 1 << 16; pro.cpu.a[4] = 0x7ffffff0 - pro.cpu.a[0] = 0x12345678; pro.cpu.a[1] = 0x3ffd0000 - pro.cpu.a[6] = 0x3ffb0000; pro.cpu.a[7] = count; pro.cpu.a[8] = seed + assert configure(machine, sender_cpu, symbols['sender']) == 0 + assert configure(machine, receiver_cpu, symbols['idle']) == 0 + tx, rx = [core(machine, cpu).contents for cpu in [sender_cpu, receiver_cpu]] + tx.ps |= 1 << 16; tx.cpu.a[4] = 0x7ffffff0 + tx.cpu.a[0] = 0x12345678; tx.cpu.a[1] = 0x3ffd0000 + tx.cpu.a[6] = 0x3ffb0000; tx.cpu.a[7] = count; tx.cpu.a[8] = seed # Distinct idle registers must survive every actual ISR. - app.cpu.a[1] = 0x3ffd8000 - for r in [0] + list(range(2, 16)): app.cpu.a[r] = 0xa5000000 + r - original = list(app.cpu.a) + rx.cpu.a[1] = 0x3ffd8000 + for r in [0] + list(range(2, 16)): rx.cpu.a[r] = 0xa5000000 + r + original = list(rx.cpu.a) general = (C.c_uint32 * 3)(*[symbols['irq_entry']] * 3) high = (C.c_uint32 * 6)(*[symbols['irq_entry']] * 6) - exceptions(C.byref(app), general); assert irq(C.byref(app), high) == 0 - app.irq.excsave[2] = 0x3ffd9000; app.irq.intenable = 1 << 19 - assert bus.write(bus.ctx, 0x3ff00278, 4, 19) == 0 + exceptions(C.byref(rx), general); assert irq(C.byref(rx), high) == 0 + rx.irq.excsave[2] = 0x3ffd9000; rx.irq.intenable = 1 << 19 + assert bus.write(bus.ctx, 0x3ff00164 + receiver_cpu * 0x114, 4, 19) == 0 # Guard both ends of the IRQ frame/leaf-call stack region. for addr in [0x3ffd8f00, 0x3ffd9000]: assert bus.write(bus.ctx, addr, 4, 0xc0decafe) == 0 for cpu in range(2): assert run(machine, cpu, 1) == 0 event = C.c_uint(); fault = Fault(); trace = []; accepted = 0; sender_done = False for turn in range(50000): cpu = (turn + trial) % 2 - if cpu == 0 and pro.cpu.pc == 0x7ffffff0: + if cpu == sender_cpu and tx.cpu.pc == 0x7ffffff0: if not sender_done: - assert run(machine, 0, 0) == 0; sender_done = True - if cpu == 0 and sender_done: + assert run(machine, sender_cpu, 0) == 0; sender_done = True + if cpu == sender_cpu and sender_done: continue status = step(machine, cpu, C.byref(event), C.byref(fault)) assert status == 0, (status, cpu, hex(fault.pc), hex(fault.opcode), fault.kind) assert event.value in [0, 12, 18], event.value - trace.append((cpu, event.value, pro.cpu.pc, app.cpu.pc)); total += event.value == 0 - if event.value == 12: accepted += 1 + trace.append((cpu, event.value, tx.cpu.pc, rx.cpu.pc)); total += event.value == 0 + if event.value == 12: + assert cpu == receiver_cpu and rx.irq.last_pending == 1 << 19 + accepted += 1 # Finish only after last ISR returned and re-entered WAITI. - if sender_done and app.irq.waiting and not (app.ps & 0x10): break - assert sender_done and accepted == count and app.irq.waiting + if sender_done and rx.irq.waiting and not (rx.ps & 0x10): break + assert sender_done and accepted == count and rx.irq.waiting value = C.c_uint32() for addr, expected in [(0x3ffb0000, seed ^ count), (0x3ffb0004, seed ^ count), (0x3ffb0008, count), (0x3ff000dc, 0), - (0x3ff000f8, 0), (0x3ffd8f00, 0xc0decafe), (0x3ffd9000, 0xc0decafe)]: + (0x3ff000ec + receiver_cpu * 12, 0), (0x3ffd8f00, 0xc0decafe), (0x3ffd9000, 0xc0decafe)]: assert bus.read(bus.ctx, addr, 4, C.byref(value)) == 0 and value.value == expected, hex(addr) - assert pro.cpu.a[6] == seed ^ count and pro.cpu.a[0] == 0x12345678 and pro.cpu.a[1] == 0x3ffd0000 - assert pro.base == 0 and pro.start == 1 and app.base == 0 and app.start == 1 - assert list(app.cpu.a) == original and app.irq.excsave[2] == 0x3ffd9000 + assert tx.cpu.a[6] == seed ^ count and tx.cpu.a[0] == 0x12345678 and tx.cpu.a[1] == 0x3ffd0000 + assert tx.base == 0 and tx.start == 1 and rx.base == 0 and rx.start == 1 + assert list(rx.cpu.a) == original and rx.irq.excsave[2] == 0x3ffd9000 + assert rx.ps == 1 << 18 and rx.irq.excsave[3] == original[1] if replay: assert trace == replay_trace else: replay_trace = trace deliveries += accepted; runs += 1 finally: destroy(machine) assert allocator.live_bytes == 0 - print(f'{runs} compiled mailbox executions, {deliveries} routed interrupts, 64 exact replays, {total} retired instructions') + print(f'{runs} compiled mailbox executions, {deliveries} routed interrupts, 128 exact replays (both directions), {total} retired instructions') if __name__ == '__main__': From ed04bf2b3bcf23ed91f1b6a77ccab65cc9995e0a Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 02:48:35 +0000 Subject: [PATCH 16/57] Implement ESP32 APP clock and stall execution controls with gate-aware timing --- CHANGELOG.md | 16 ++- core/mcu/esp32/machine.c | 36 +++++++ core/mcu/esp32/machine.h | 24 ++++- docs/STATUS.md | 15 ++- docs/engineering/esp32-dual-core.md | 81 +++++++++++++- tests/unit/test_esp32_controls.c | 105 +++++++++++++++++++ tests/unit/test_esp32_ipi.c | 1 + tests/unit/test_esp32_machine.c | 4 +- tests/vendor/README.md | 10 ++ tests/vendor/xtensa_integer/run_controls.py | 69 ++++++++++++ tests/vendor/xtensa_integer/run_ipi_c.py | 37 +++++-- tests/vendor/xtensa_integer/run_machine_c.py | 5 +- 12 files changed, 380 insertions(+), 23 deletions(-) create mode 100644 tests/unit/test_esp32_controls.c create mode 100644 tests/vendor/xtensa_integer/run_controls.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c54cecced..77d951003 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental ESP32 APP execution controls + +- Added DPORT APP clock-enable/run-stall and ROM-consumed boot-address fields. + APP starts clock-disabled; debug fixtures explicitly enable it. Gate/stall + suspends fetch, interrupt entry and retirement while preserving CPU/memory + state, including after a firmware store suspends its own execution. +- Added gate-aware supplied-clock/deadline APIs. No automatic clock ticks, + physical synchronization/pulse timing or power behavior is claimed. Reset + control remains unsupported; writing the boot address does not set PC or + replace the missing ROM boot sequence. +- Added 163 native assertions, 384 pinned-header field comparisons and compiled + control/mailbox acceptance: 256 runs, 4,224 interrupts, 128 exact replays and + 551,232 retired instructions with PRO suspending/resuming APP during traffic. + ### Experimental ESP32 interrupt matrix and cross-core signaling - Added per-core DPORT source maps/status, shared software-generated interrupts, @@ -15,7 +29,7 @@ versions follow semantic versioning. There is no release yet. instruction commit. A peer can wake from WAITI, acknowledge a source and RFI; paused-peer edges and same-core MMIO IRQ updates survive correctly. No physical APB timing, DPORT reset/stall, cache/flash, full SMP or boot claim is added. -- Added 64,374 matrix/101 integration assertions and pinned-header referees for +- Added 64,374 matrix/103 integration assertions and pinned-header referees for 4,288 routes/512 combined-source cases. Actual ESP32 C sender/C handler with an assembled entry/return wrapper passes 256 mailbox runs, 4,224 interrupts and 128 exact replays (512,128 retired instructions), preserving integer registers and stack guards. diff --git a/core/mcu/esp32/machine.c b/core/mcu/esp32/machine.c index 1ffd533d8..ef3466ea0 100644 --- a/core/mcu/esp32/machine.c +++ b/core/mcu/esp32/machine.c @@ -6,6 +6,7 @@ struct machine_view { struct esk_esp32_machine *m;unsigned cpu; }; struct esk_esp32_machine { esk_alloc *a;esk_esp32_fabric *fabric; esk_xtensa_windows cpu[2];uint64_t retired[2];unsigned running[2],executing; + uint32_t app_clock,app_stall,app_boot; esk_esp32_matrix matrix;struct machine_view view[2]; }; /* Never mutate an executing CPU from a bus callback. Synchronize the @@ -23,9 +24,29 @@ static void sync_inputs(esk_esp32_machine *m) } } } +/* Only documented APP clock/stall and ROM-consumed boot-address fields. + * Reset remains unsupported: treating BOOT_ADDR as the hardware reset PC + * would bypass ROM and fabricate a successful boot. */ +static es_status control_read(const esk_esp32_machine *m,uint32_t address,unsigned bytes,uint32_t *value) +{ + if(address<0x3ff00030 || address>0x3ff0003b) return ES_EUNSUPPORTED; + if(!value || bytes!=4 || (address&3u)) return ES_EINVAL; + *value=address==0x3ff00030 ? m->app_clock : address==0x3ff00034 ? m->app_stall : m->app_boot; + return ES_OK; +} +static es_status control_write(esk_esp32_machine *m,uint32_t address,unsigned bytes,uint32_t value) +{ + if(address<0x3ff00030 || address>0x3ff0003b) return ES_EUNSUPPORTED; + if(bytes!=4 || (address&3u)) return ES_EINVAL; + if(address==0x3ff00030) m->app_clock=value&1u; + else if(address==0x3ff00034) m->app_stall=value&1u; + else m->app_boot=value; + return ES_OK; +} static es_status bus_read(void *ctx,uint32_t address,unsigned bytes,uint32_t *value) { struct machine_view *v=(struct machine_view *)ctx;esk_xtensa_memory ram;es_status st; + st=control_read(v->m,address,bytes,value);if(st!=ES_EUNSUPPORTED) return st; st=esk_esp32_matrix_read(&v->m->matrix,address,bytes,value); if(st!=ES_EUNSUPPORTED) return st; ram=esk_esp32_fabric_bus(v->m->fabric,v->cpu);return ram.read(ram.ctx,address,bytes,value); @@ -33,6 +54,7 @@ static es_status bus_read(void *ctx,uint32_t address,unsigned bytes,uint32_t *va static es_status bus_write(void *ctx,uint32_t address,unsigned bytes,uint32_t value) { struct machine_view *v=(struct machine_view *)ctx;esk_xtensa_memory ram;es_status st; + st=control_write(v->m,address,bytes,value);if(st!=ES_EUNSUPPORTED) return st; st=esk_esp32_matrix_write(&v->m->matrix,address,bytes,value); if(st!=ES_EUNSUPPORTED) { if(st==ES_OK && !v->m->executing) sync_inputs(v->m); @@ -71,6 +93,19 @@ es_status esk_esp32_machine_nmi_mask(esk_esp32_machine *m,unsigned cpu,int asser es_status st;if(!m || m->executing) return ES_EINVAL; st=esk_esp32_matrix_nmi_mask(&m->matrix,cpu,asserted);if(st==ES_OK) sync_inputs(m);return st; } +es_status esk_esp32_machine_clock_advance(esk_esp32_machine *m,unsigned cpu,uint64_t ticks) +{ + if(!m || cpu>1 || m->executing) return ES_EINVAL; + if(!m->cpu[cpu].irq.enabled) return ES_EUNSUPPORTED; + if(cpu==1 && !m->app_clock) return ES_OK; + return esk_xtensa_clock_advance(&m->cpu[cpu],ticks); +} +int esk_esp32_machine_clock_deadline(const esk_esp32_machine *m,unsigned cpu,uint64_t *ticks) +{ + if(ticks) *ticks=0; + if(!m || cpu>1 || !ticks || (cpu==1 && !m->app_clock)) return 0; + return esk_xtensa_clock_deadline(&m->cpu[cpu],ticks); +} es_status esk_esp32_machine_create(esk_alloc *a,esk_esp32_machine **out) { esk_esp32_machine *m;es_status st;unsigned i; @@ -107,6 +142,7 @@ es_status esk_esp32_machine_step(esk_esp32_machine *m,unsigned cpu,unsigned *eve if(fault) memset(fault,0,sizeof *fault); if(!m || cpu>1 || !event) return ES_EINVAL; if(!m->running[cpu]) { *event=19;return ES_OK; } + if(cpu==1 && (!m->app_clock || m->app_stall)) { *event=!m->app_clock ? 20u : 21u;return ES_OK; } sync_inputs(m);bus=esk_esp32_machine_bus(m,cpu);m->executing=1; st=esk_xtensa_window_step(&m->cpu[cpu],&bus,event,fault); m->executing=0;sync_inputs(m); diff --git a/core/mcu/esp32/machine.h b/core/mcu/esp32/machine.h index 30c57cfd2..aa8123bdf 100644 --- a/core/mcu/esp32/machine.h +++ b/core/mcu/esp32/machine.h @@ -8,13 +8,18 @@ typedef struct esk_esp32_machine esk_esp32_machine; /* Experimental two-context execution harness over shared internal memory. * Deterministic serialized instruction turns, not a cycle scheduler or full SoC. - * No DPORT core-reset/stall registers, boot ROM, peripheral generators, + * No DPORT core reset/release, boot ROM, peripheral generators, * cache arbitration/coherence or board model. Host controls are debug/test * operations, not emulated hardware control registers. No concurrent callers. */ es_status esk_esp32_machine_create(esk_alloc *a,esk_esp32_machine **out); void esk_esp32_machine_destroy(esk_esp32_machine *m); esk_esp32_fabric *esk_esp32_machine_fabric(esk_esp32_machine *m); -/* Unified data bus includes interrupt-matrix MMIO; fetch uses instruction +/* APP clock enable/stall initialize to documented zero; explicitly enable + * 0x3ff00030 before executing APP. 0x3ff00034 stalls/resumes without reset. + * 0x3ff00038 is the ROM-consumed boot address, not a direct PC setter. + * Reset register 0x3ff0002c remains unsupported. These functional controls + * do not qualify clock-domain synchronizers, retention or physical latency. */ +/* Unified data bus includes interrupt-matrix and APP-control MMIO; fetch uses instruction * memory and CAS remains SRAM-only. * Direct host bus calls are permitted only between turns. Matrix peripheral * inputs belong exclusively to the machine: use source/GPIO inputs below, @@ -24,13 +29,24 @@ esk_xtensa_memory esk_esp32_machine_bus(esk_esp32_machine *m,unsigned cpu); es_status esk_esp32_machine_source(esk_esp32_machine *m,unsigned source,int asserted); es_status esk_esp32_machine_gpio_irq(esk_esp32_machine *m,unsigned cpu,int nmi,int asserted); es_status esk_esp32_machine_nmi_mask(esk_esp32_machine *m,unsigned cpu,int asserted); +/* Supply clock ticks through the gate-aware machine boundary. APP ticks are + * dropped while its clock is disabled; enabled-clock ticks still advance the + * functional counter during stall/WAITI/debug pause. No wall-time conversion, + * synchronizer latency or physical debug-clock policy is implied. Direct + * low-level clock_advance(core) is a debug bypass and does not honor the gate. */ +es_status esk_esp32_machine_clock_advance(esk_esp32_machine *m,unsigned cpu,uint64_t ticks); +int esk_esp32_machine_clock_deadline(const esk_esp32_machine *m,unsigned cpu,uint64_t *ticks); /* Clear one architecture profile, pause it and set explicit entry. Shared - * memory and per-core remap survive. This is not a silicon reset sequence. */ + * memory, matrix, APP controls and per-core remap survive. + * This is not a silicon reset sequence. */ es_status esk_esp32_machine_configure(esk_esp32_machine *m,unsigned cpu,uint32_t entry); /* Mutable profile inspection/setup only between execution turns. */ esk_xtensa_windows *esk_esp32_machine_core(esk_esp32_machine *m,unsigned cpu); es_status esk_esp32_machine_run_core(esk_esp32_machine *m,unsigned cpu,int run); -/* Paused core: event 19, no fetch. Fault pauses that core at the failing PC. +/* Paused core: event 19; APP clock disabled: 20; APP stalled: 21. No fetch + * or interrupt delivery/retirement while suspended. Host pause takes priority + * over clock gate, which takes priority over stall. Functional input latches still update; gated pulse sampling is unqualified. + * Fault pauses that core at the failing PC. * Interrupt/window/wait events retain their execution-engine meanings. */ es_status esk_esp32_machine_step(esk_esp32_machine *m,unsigned cpu,unsigned *event,esk_xtensa_fault *fault); /* One turn per core, 0 then 1; stop at first fault. Earlier successful turns diff --git a/docs/STATUS.md b/docs/STATUS.md index e52356b07..e2379dc6e 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -55,7 +55,7 @@ two-context harness shares this RAM while keeping CPU/interrupt/clock state private. Native fixtures cover address/permission/remap/fault/wait boundaries; 512 actual ESP32 compiled task executions and 128 replay comparisons pass through the native bus (229,322 instructions). This remains a bounded memory/execution -harness: cache/MMU, DPORT core reset/stall controls, complete SMP and SoC boot are absent. +harness: cache/MMU, DPORT core reset/release controls, complete SMP and SoC boot are absent. L32AI/S32RI now provide acquire/release ordering under the synchronous completed callback contract (30,732 native assertions). The compiled shared-bus acceptance includes acquire/CAS/release spinlocks protecting ordinary writes. Asynchronous @@ -65,12 +65,21 @@ software-generated interrupts, shared OR routes, disconnected internal-input selectors, core-local GPIO and explicit PID NMI-mask inputs. Firmware can wake a peer from WAITI, acknowledge its source and return. Delivery synchronizes after instruction commit, including paused-peer edges. New fixtures pass 64,374 matrix -and 101 integration assertions; pinned-header referees pass 4,288 routes and 512 +and 103 integration assertions; pinned-header referees pass 4,288 routes and 512 combined-source/mask cases. Actual ESP32 C sender/C handler acceptance with an assembled entry/return wrapper passes 256 mailbox executions, 4,224 interrupts and 128 exact replays (512,128 retired instructions), in both directions. Peripheral generators, PID hardware, DPORT -reset/stall, APB timing, +reset/release, APB timing, flash/cache and boot remain absent; no complete ISR/RTOS or timing claim follows. +APP clock/stall fields and the ROM-consumed boot-address register now work +through the machine bus. APP starts clock-disabled; fixture setup explicitly +enables it. Gate/stall stops fetch/retirement and resumes at the same PC, without +resetting shared state. Gate-aware supplied-clock/deadline APIs suppress APP +counter ticks while gated. New evidence: 163 native assertions, 384 vendor-field +comparisons and compiled control/mailbox stress (256 runs, 4,224 interrupts, +128 replays, 551,232 retired instructions). Reset/release remains unsupported; +the boot-address register does not bypass missing ROM code or set PC directly. +Physical clock/pulse timing, power and cold boot are still unqualified. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 0ed1f281d..36ac36bfc 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -1,6 +1,6 @@ # Original ESP32 dual-core target: identity and implementation gates -**Status: firmware inspection, experimental Xtensa execution, shared internal RAM and interrupt routing implemented; no runnable board target is added.** Requested by +**Status: firmware inspection, experimental Xtensa execution, shared internal RAM, interrupt routing and APP execution controls implemented; no runnable board target is added.** Requested by the owner on 2026-11-04. This is the original ESP32 family, not ESP32-C3, ESP32-S2 or ESP32-S3. The generic marketplace title “ESP-32 / ESP-32S WiFi+Bluetooth Dual Cores” does not identify a reproducible board revision. @@ -513,12 +513,12 @@ advancement remains explicit and private to each profile. All calls have one serialized owner. Native SRAM1/2 CAS performs its read and conditional write within one callback, so instruction-interleaved contexts cannot intervene. This is not host-thread-safe memory, cache/coherence, -arbitration, an instruction-to-cycle model, DPORT reset/release/stall, retention, +arbitration, an instruction-to-cycle model, DPORT reset/release, physical clock timing, retention, DMA or a complete SMP machine. Reconfiguring one profile preserves shared RAM and remap settings and is explicitly not a silicon reset sequence. No CPU constructor, board picker entry, boot ROM execution or ESP-IDF boot is added. -Native fixtures pass 360,531 memory assertions and 84 machine assertions: every +Native fixtures pass 360,531 memory assertions and 86 machine assertions: every SRAM1 word and byte lane, remap isolation, RTC restrictions, rejected access widths/holes, all-or-nothing loading, allocation failure, shared conditional stores, independent state, fault ordering and @@ -588,7 +588,7 @@ Proof: - `test_esp32_matrix`: 64,374 assertions across every defined source/destination, reset fields, core isolation, GPIO-specific status, shared OR, remapping, NMI masks, software-source fields, RO status and invalid accesses. -- `test_esp32_ipi`: 101 assertions for a fetched sender, peer WAITI wake, +- `test_esp32_ipi`: 103 assertions for a fetched sender, peer WAITI wake, handler acknowledgement/RFI, paused-peer edge capture, same-core MMIO changes surviving instruction commit, shared edge-input OR and internal-input isolation. - `run_matrix.py`: 4,288 independent vendor-addressed routes and 512 combined @@ -626,6 +626,79 @@ python tests/vendor/xtensa_integer/run_ipi_c.py \ --nm /path/to/xtensa-esp32-elf-nm ``` +## Implemented: APP clock/stall controls and ROM-consumed boot address + +The unified machine bus now implements these TRM v5.9 chapter 12 fields, +checked against the same pinned ESP-IDF `dport_reg.h` above: + +| Register | Address | Defined field | Initial field value | +| --- | --- | --- | --- | +| APPCPU_CTRL_B | `3FF00030` | Bit 0: APP clock enable | 0 | +| APPCPU_CTRL_C | `3FF00034` | Bit 0: APP run-stall | 0 | +| APPCPU_CTRL_D | `3FF00038` | 32-bit ROM-consumed boot address | 0 | + +APP therefore requires an explicit clock-enable write before fixture execution. +`machine_configure` injects a debug architecture profile and entry, not a hardware +boot/reset operation: it preserves these controls, memory, remap and matrix. +Reserved control bits read zero and are ignored on writes. Only aligned 32-bit +data accesses are qualified; malformed accesses do not change control state. + +A debug-paused core reports event 19. Otherwise APP reports event 20 if its clock +is disabled, or event 21 if stalled with its clock enabled. These turns do not +fetch, deliver interrupts or retire instructions; PC/registers and shared RAM +survive. A firmware store that disables its own execution commits before the +next suspended turn. PRO can resume APP through the same registers. Clock gate +has priority over stall; debug pause has priority over both. These are functional +instruction-boundary decisions, not a model of clock-domain synchronizer or +pipeline drain latency. Input routing remains functional at explicit boundaries; +short-pulse capture while the CPU clock is gated is not silicon-qualified. + +`machine_clock_advance` accepts supplied clock ticks and discards APP ticks while +its clock is disabled, without catch-up on re-enable. The machine deadline API +reports no APP compare deadline while gated. With its clock enabled, supplied +ticks continue advancing the functional counter during stall, WAITI or a host +debug pause. This does not establish physical debug-clock policy or elapsed +wall time. The lower-level `clock_advance(core)` remains an explicit debug bypass +that does not honor machine gates. No ticks are inferred from instructions. +Pending requests remain pending while suspended and can enter on resume. + +The boot-address register stores all 32 bits and never directly changes PC. +The TRM explicitly says ROM code jumps to this address after APP boots. That ROM +path is not implemented. In particular, APPCPU_CTRL_A reset/release at +`3FF0002C` remains unsupported; clock enable alone is not a real cold-boot +sequence. No synthetic reset state, ROM jump, reset cause/retention domain, +oscillator/PLL/APB conversion, power consumption or complete board boot is added. + +Proof: + +- `test_esp32_controls`: 163 assertions for initial/read/write fields, invalid + accesses, self gate/stall and resume, suspended-round isolation, state/memory + preservation, gate-aware clock/deadline behavior and pending interrupt delivery. +- `run_controls.py`: 384 field comparisons through both CPU bus views, using + parsed pinned-vendor register addresses, field widths and defaults. +- `run_ipi_c.py --exercise-controls`: PRO C firmware stalls and gates APP around + each message, writes/reads the boot-address field and raises a held source + before releasing APP. The baseline bidirectional workload still passes, while + the control probe additionally checks unchanged PC/registers on suspended + turns and final control fields. It passes 256 mailbox runs, 4,224 interrupts, + 128 exact replays and 551,232 retired instructions. Its 27,456 gated and + 10,560 stalled turns are attempted instruction boundaries, not physical cycles. +- Baseline mailbox acceptance remains 512,128 retired instructions and the native + shared-RAM/spinlock acceptance remains 229,322 instructions. Their fixture + setup now explicitly enables APP's clock instead of bypassing its default. + +```sh +python tests/vendor/xtensa_integer/run_controls.py \ + --dport-reg /path/to/esp-idf/components/soc/esp32/include/soc/dport_reg.h \ + --soc-header /path/to/esp-idf/components/soc/esp32/include/soc/soc.h +python tests/vendor/xtensa_integer/run_ipi_c.py \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld \ + --nm /path/to/xtensa-esp32-elf-nm \ + --exercise-controls +``` + ## Implemented: standalone firmware integrity inspection `embsim image-info esp32 app.bin` inspects a plaintext original-ESP32 image diff --git a/tests/unit/test_esp32_controls.c b/tests/unit/test_esp32_controls.c new file mode 100644 index 000000000..f41490265 --- /dev/null +++ b/tests/unit/test_esp32_controls.c @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/machine.h" +static void register_fields_and_suspend(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_xtensa_memory bus;esk_xtensa_windows *app; + esk_xtensa_fault fault;uint32_t v=99;unsigned event,i;const uint32_t values[]={0,1,2,UINT32_MAX,0x80000000}; + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK);bus=esk_esp32_machine_bus(m,0); + for(i=0;i<3;i++) { CHECK_EQ(bus.read(bus.ctx,0x3ff00030u+4u*i,4,&v),ES_OK);CHECK_EQ(v,0); } + CHECK_EQ(esk_esp32_machine_configure(m,1,0x1234),ES_OK);app=esk_esp32_machine_core(m,1);app->cpu.a[5]=77; + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,19); + CHECK_EQ(esk_esp32_machine_run_core(m,1,1),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,20); + CHECK_EQ(bus.write(bus.ctx,0x3ff00034,4,1),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,20); + CHECK_EQ(bus.write(bus.ctx,0x3ff00030,4,1),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,21); + CHECK_EQ(app->cpu.pc,0x1234);CHECK_EQ(app->cpu.a[5],77);CHECK_EQ(esk_esp32_machine_retired(m,1),0); + for(i=0;icpu.pc,0x1234); + } + CHECK_EQ(bus.write(bus.ctx,0x3ff00030,2,1),ES_EINVAL); + CHECK_EQ(bus.write(bus.ctx,0x3ff00031,4,1),ES_EINVAL); + CHECK_EQ(bus.write(bus.ctx,0x3ff00038,1,1),ES_EINVAL); + CHECK_EQ(bus.write(bus.ctx,0x3ff0002c,4,0),ES_EUNSUPPORTED); + CHECK_EQ(bus.read(bus.ctx,0x3ff0002c,4,&v),ES_EUNSUPPORTED); + CHECK_EQ(bus.fetch(bus.ctx,0x3ff00030,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(bus.compare_exchange(bus.ctx,0x3ff00030,0,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(bus.read(bus.ctx,0x3ff00030,4,&v),ES_OK);CHECK_EQ(v,0); + CHECK_EQ(bus.read(bus.ctx,0x3ff00038,4,&v),ES_OK);CHECK_EQ(v,0x80000000u); + CHECK_EQ(esk_esp32_machine_configure(m,1,0x50000000),ES_OK); + CHECK_EQ(bus.read(bus.ctx,0x3ff00030,4,&v),ES_OK);CHECK_EQ(v,0); + CHECK_EQ(bus.read(bus.ctx,0x3ff00038,4,&v),ES_OK);CHECK_EQ(v,0x80000000u); + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +static void firmware_self_suspend(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_xtensa_memory bus;esk_xtensa_windows *app,*pro; + esk_xtensa_fault fault;unsigned event,events[2],failed;uint32_t v; + unsigned char code[]={0x22,0x63,0,0x22,0x64,0,0x3d,0xf0}; /* stores to A3/A4, NOP */ + unsigned char nops[]={0x3d,0xf0,0x3d,0xf0}; + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK);bus=esk_esp32_machine_bus(m,0); + CHECK_EQ(esk_esp32_fabric_load(esk_esp32_machine_fabric(m),0,0x40080000,code,sizeof code),ES_OK); + CHECK_EQ(esk_esp32_fabric_load(esk_esp32_machine_fabric(m),0,0x40080100,nops,sizeof nops),ES_OK); + CHECK_EQ(esk_esp32_machine_configure(m,1,0x40080000),ES_OK);app=esk_esp32_machine_core(m,1); + CHECK_EQ(esk_esp32_machine_configure(m,0,0x40080100),ES_OK);pro=esk_esp32_machine_core(m,0); + app->cpu.a[2]=0;app->cpu.a[3]=0x3ff00030;app->cpu.a[4]=0x3ff00034; + CHECK_EQ(bus.write(bus.ctx,0x3ffb0000,4,0xdeadbeef),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff00030,4,1),ES_OK);CHECK_EQ(esk_esp32_machine_run_core(m,1,1),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,0);CHECK_EQ(app->cpu.pc,0x40080003); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,20); + CHECK_EQ(bus.write(bus.ctx,0x3ff00030,4,1),ES_OK);app->cpu.a[2]=1; + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,0);CHECK_EQ(app->cpu.pc,0x40080006); + CHECK_EQ(esk_esp32_machine_run_core(m,0,1),ES_OK); + CHECK_EQ(esk_esp32_machine_round(m,events,&failed,&fault),ES_OK);CHECK_EQ(failed,2); + CHECK_EQ(events[0],0);CHECK_EQ(events[1],21);CHECK_EQ(pro->cpu.pc,0x40080102); + CHECK_EQ(bus.write(bus.ctx,0x3ff00034,4,0),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,0);CHECK_EQ(app->cpu.pc,0x40080008); + CHECK_EQ(esk_esp32_machine_retired(m,1),3);CHECK_EQ(app->cpu.a[3],0x3ff00030); + CHECK_EQ(bus.read(bus.ctx,0x3ffb0000,4,&v),ES_OK);CHECK_EQ(v,0xdeadbeefu); + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +static void gated_clock_and_pending_interrupt(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_xtensa_memory bus;esk_xtensa_windows *app,*pro; + esk_xtensa_fault fault;unsigned event,cpu;uint64_t ticks=99; + const uint32_t general[3]={0x40081000,0x40081040,0x40081080}; + const uint32_t high[6]={0x40082000,0x40082040,0x40082080,0x400820c0,0x40082100,0x40082140}; + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK);bus=esk_esp32_machine_bus(m,0); + for(cpu=0;cpu<2;cpu++) { + esk_xtensa_windows *w; + CHECK_EQ(esk_esp32_machine_configure(m,cpu,0x40080000),ES_OK);w=esk_esp32_machine_core(m,cpu); + esk_xtensa_exception_configure(w,general);CHECK_EQ(esk_xtensa_irq_configure(w,high),ES_OK); + CHECK_EQ(esk_xtensa_irq_write(w,240,10),ES_OK); + } + app=esk_esp32_machine_core(m,1);pro=esk_esp32_machine_core(m,0);app->irq.intenable=1u<<6; + CHECK_EQ(esk_esp32_machine_run_core(m,1,1),ES_OK); + CHECK_EQ(esk_esp32_machine_clock_advance(m,1,100),ES_OK);CHECK_EQ(app->irq.ccount,0); + CHECK_EQ(esk_esp32_machine_clock_deadline(m,1,&ticks),0);CHECK_EQ(ticks,0); + CHECK_EQ(bus.write(bus.ctx,0x3ff00030,4,1),ES_OK);CHECK_EQ(bus.write(bus.ctx,0x3ff00034,4,1),ES_OK); + CHECK_EQ(esk_esp32_machine_clock_deadline(m,1,&ticks),1);CHECK_EQ(ticks,10); + CHECK_EQ(esk_esp32_machine_clock_advance(m,1,10),ES_OK);CHECK_EQ(app->irq.ccount,10);CHECK_EQ(pro->irq.ccount,0); + CHECK_EQ(esk_xtensa_irq_pending(app),1u<<6); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,21);CHECK_EQ(app->cpu.pc,0x40080000); + CHECK_EQ(bus.write(bus.ctx,0x3ff00030,4,0),ES_OK); + CHECK_EQ(esk_esp32_machine_clock_advance(m,1,UINT64_MAX),ES_OK);CHECK_EQ(app->irq.ccount,10); + CHECK_EQ(bus.write(bus.ctx,0x3ff00034,4,0),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,20); + CHECK_EQ(bus.write(bus.ctx,0x3ff00030,4,1),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,11);CHECK_EQ(app->cpu.pc,general[0]); + CHECK_EQ(esk_esp32_machine_retired(m,1),0);CHECK_EQ(app->epc1,0x40080000); + CHECK_EQ(esk_esp32_machine_clock_advance(m,0,3),ES_OK);CHECK_EQ(pro->irq.ccount,3);CHECK_EQ(app->irq.ccount,10); + CHECK_EQ(esk_esp32_machine_clock_advance(m,2,1),ES_EINVAL); + CHECK_EQ(esk_esp32_machine_clock_deadline(m,2,&ticks),0);CHECK_EQ(ticks,0); + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +TEST_MAIN_BEGIN + (void)t_str_eq;RUN(register_fields_and_suspend);RUN(firmware_self_suspend);RUN(gated_clock_and_pending_interrupt); +TEST_MAIN_END diff --git a/tests/unit/test_esp32_ipi.c b/tests/unit/test_esp32_ipi.c index 37366ccc6..49249e1d8 100644 --- a/tests/unit/test_esp32_ipi.c +++ b/tests/unit/test_esp32_ipi.c @@ -8,6 +8,7 @@ static void profile(esk_esp32_machine *m,unsigned cpu,uint32_t entry) { esk_xtensa_windows *w; CHECK_EQ(esk_esp32_machine_configure(m,cpu,entry),ES_OK); + if(cpu==1) { esk_xtensa_memory bus=esk_esp32_machine_bus(m,0);CHECK_EQ(bus.write(bus.ctx,0x3ff00030,4,1),ES_OK); } w=esk_esp32_machine_core(m,cpu);esk_xtensa_exception_configure(w,general); CHECK_EQ(esk_xtensa_irq_configure(w,high),ES_OK); } diff --git a/tests/unit/test_esp32_machine.c b/tests/unit/test_esp32_machine.c index 57505aa8b..e43c197d8 100644 --- a/tests/unit/test_esp32_machine.c +++ b/tests/unit/test_esp32_machine.c @@ -9,7 +9,8 @@ static void shared_execution(void) /* S32C1I A2,A3,0 followed by NOP.N. Independently assembled ESP32 opcode. */ unsigned char code[]={0x22,0xe3,0x00,0x3d,0xf0}; esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK); - f=esk_esp32_machine_fabric(m);bus=esk_esp32_fabric_bus(f,0); + f=esk_esp32_machine_fabric(m);bus=esk_esp32_machine_bus(m,0); + CHECK_EQ(bus.write(bus.ctx,0x3ff00030,4,1),ES_OK); CHECK_EQ(esk_esp32_fabric_load(f,0,0x40080000,code,sizeof code),ES_OK); CHECK_EQ(esk_esp32_machine_configure(m,0,0x40080000),ES_OK); CHECK_EQ(esk_esp32_machine_configure(m,1,0x40080000),ES_OK); @@ -62,6 +63,7 @@ static void independent_wait_and_clock(void) CHECK_EQ(esk_esp32_machine_run_core(m,i,1),ES_OK); } one=esk_esp32_machine_core(m,0);two=esk_esp32_machine_core(m,1); + { esk_xtensa_memory bus=esk_esp32_machine_bus(m,0);CHECK_EQ(bus.write(bus.ctx,0x3ff00030,4,1),ES_OK); } CHECK_EQ(esk_esp32_machine_round(m,events,&failed,&fault),ES_OK); CHECK_EQ(one->irq.waiting,1);CHECK_EQ(two->irq.waiting,0); CHECK_EQ(esk_esp32_machine_round(m,events,&failed,&fault),ES_OK); diff --git a/tests/vendor/README.md b/tests/vendor/README.md index fb763848d..d5a5ec69d 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -102,3 +102,13 @@ and APP-to-PRO directions pass. No Python memory/interrupt callbacks: 128 exact replays, 512,128 retired instructions. Checks messages, source clear, stack guards, general registers and ABI/window return. Not full ISR/RTOS, PID, peripheral generators, APB timing or SoC boot qualification. + +`xtensa_integer/run_controls.py` parses the same pinned `--dport-reg` and +`--soc-header` for APP clock/stall/boot-address register addresses, widths and +defaults: 384 field comparisons across both CPU bus views. Reset/ROM boot are +not qualified. Add `--exercise-controls` to `run_ipi_c.py` to have compiled PRO +firmware suspend/resume APP during message/interrupt traffic: 256 runs, +4,224 interrupts, 128 exact replays and 551,232 retired instructions. It checks +unchanged PC/registers on suspended turns and final control fields. Its counted +suspended turns are instruction attempts, not physical clock cycles. All machine +fixture runners now explicitly enable the documented APP clock field. diff --git a/tests/vendor/xtensa_integer/run_controls.py b/tests/vendor/xtensa_integer/run_controls.py new file mode 100644 index 000000000..0655bc788 --- /dev/null +++ b/tests/vendor/xtensa_integer/run_controls.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""Check native APP controls using pinned vendor addresses, widths and defaults. + +External headers provide facts only; no vendor implementation is linked. Reset +and ROM boot are outside this qualified execution-control subset. +""" +import argparse +import ctypes as C +from pathlib import Path +import random +import re +import subprocess +import tempfile +from run_execute import ROOT, Memory +from run_machine_c import Allocator +from run_matrix import read_header + + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument('--dport-reg', type=Path, required=True) + ap.add_argument('--soc-header', type=Path, required=True) + ap.add_argument('--cc', default='cc'); args = ap.parse_args() + dport = read_header(args.dport_reg, '38deaa4ef02016ee9cdf490a9bfe4b587f11d1d1e6a2329dc858a50b9b248817') + soc = read_header(args.soc_header, '7a7148c288d7c289c961810e5617fa7b14d105b28349a45f772150b1e0845dd3') + base = int(re.search(r'#define\s+DR_REG_DPORT_BASE\s+(0x[0-9a-f]+)', soc)[1], 16) + fields = [] + for letter in 'BCD': + match = re.search(r'#define\s+DPORT_APPCPU_CTRL_' + letter + r"_REG\s+\(DR_REG_DPORT_BASE\s*\+\s*(0x[0-9A-F]+)\)\n" + r"/\* \w+ : R/W ;bitpos:\[(\d+)(?::(\d+))?\] ;default: \d+'([bh])([0-9a-fA-F]+)", dport) + assert match, letter + offset, high, low, radix, default = match.groups(); high = int(high); low = int(low or high) + fields.append((base + int(offset, 16), ((1 << (high - low + 1)) - 1) << low, + int(default, 2 if radix == 'b' else 16))) + rng = random.Random(0xc10c); checks = 0 + with tempfile.TemporaryDirectory(prefix='embsim-controls-') as tmp: + lib = Path(tmp) / 'controls.so' + sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c']] + sources += [ROOT / 'core/util/alloc.c'] + subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', + '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) + api = C.CDLL(str(lib)) + api.esk_alloc_init.argtypes = [C.POINTER(Allocator), C.c_void_p]; api.esk_alloc_init.restype = None + api.esk_esp32_machine_create.argtypes = [C.POINTER(Allocator), C.POINTER(C.c_void_p)] + api.esk_esp32_machine_destroy.argtypes = [C.c_void_p]; api.esk_esp32_machine_destroy.restype = None + api.esk_esp32_machine_bus.argtypes = [C.c_void_p, C.c_uint]; api.esk_esp32_machine_bus.restype = Memory + for cpu in range(2): + allocator = Allocator(); api.esk_alloc_init(C.byref(allocator), None) + machine = C.c_void_p(); assert api.esk_esp32_machine_create(C.byref(allocator), C.byref(machine)) == 0 + try: + bus = api.esk_esp32_machine_bus(machine, cpu); value = C.c_uint32() + for address, mask, default in fields: + assert bus.read(bus.ctx, address, 4, C.byref(value)) == 0 and value.value == default + for trial in range(64): + written = [0, 1, 2, 0xffffffff, 0x80000000][trial] if trial < 5 else rng.getrandbits(32) + assert bus.write(bus.ctx, address, 4, written) == 0 + assert bus.read(bus.ctx, address, 4, C.byref(value)) == 0 and value.value == written & mask + checks += 1 + finally: + api.esk_esp32_machine_destroy(machine) + assert allocator.live_bytes == 0 + print(f'{checks} vendor-addressed APP control field comparisons pass across both CPU bus views') + + +if __name__ == '__main__': + main() diff --git a/tests/vendor/xtensa_integer/run_ipi_c.py b/tests/vendor/xtensa_integer/run_ipi_c.py index e3126d674..8275ce4ec 100644 --- a/tests/vendor/xtensa_integer/run_ipi_c.py +++ b/tests/vendor/xtensa_integer/run_ipi_c.py @@ -18,12 +18,26 @@ from run_machine_c import Allocator SENDER = ''' -unsigned sender(volatile unsigned *mailbox,unsigned count,unsigned seed) { +unsigned sender(volatile unsigned *mailbox,unsigned count,unsigned seed,unsigned controls) { volatile unsigned *ipi=(volatile unsigned *)0x3ff000dc; unsigned i; for(i=1;i<=count;i++) { + #if CONTROL_PROBE + if(controls) { + *(volatile unsigned *)0x3ff00034=1; + *(volatile unsigned *)0x3ff00030=0; + *(volatile unsigned *)0x3ff00038=seed^i; + if(*(volatile unsigned *)0x3ff00038!=(seed^i)) return ~(seed^i); + } +#endif mailbox[0]=seed^i; *ipi=1; +#if CONTROL_PROBE + if(controls) { + *(volatile unsigned *)0x3ff00030=1; + *(volatile unsigned *)0x3ff00034=0; + } +#endif while(__atomic_load_n(mailbox+1,__ATOMIC_ACQUIRE)!=(seed^i)) { } } return mailbox[1]; @@ -46,14 +60,14 @@ def main(): ap = argparse.ArgumentParser(description=__doc__) for name in ['compiler', 'objcopy', 'linker', 'nm']: ap.add_argument('--' + name, required=True) - ap.add_argument('--cc', default='cc'); args = ap.parse_args() + ap.add_argument('--cc', default='cc'); ap.add_argument('--exercise-controls', action='store_true'); args = ap.parse_args() with tempfile.TemporaryDirectory(prefix='embsim-ipi-c-') as tmp: out = Path(tmp); objects = [] for name, source in [('sender', SENDER), ('handler', HANDLER)]: src, obj = out / (name + '.c'), out / (name + '.o'); objects.append(obj) src.write_text(source, encoding='utf-8') subprocess.run([args.compiler, '-B' + str(Path(args.compiler).parent) + '/', '-O1', - '-mtext-section-literals', '-ffreestanding', '-fno-builtin', '-c', str(src), '-o', str(obj)], check=True) + '-DCONTROL_PROBE=' + str(int(args.exercise_controls)), '-mtext-section-literals', '-ffreestanding', '-fno-builtin', '-c', str(src), '-o', str(obj)], check=True) # Fixture IRQ prologue/epilogue saves the interrupted integer context # on an EXCSAVE2 stack, using EXCSAVE3 to save the idle SP. Not an RTOS handler. entry = ['.text', '.align 4', @@ -97,7 +111,7 @@ def bind(name, result, *params): step = bind('esk_esp32_machine_step', C.c_int, pointer, C.c_uint, C.POINTER(C.c_uint), C.POINTER(Fault)) exceptions = bind('esk_xtensa_exception_configure', None, wp, C.POINTER(C.c_uint32)) irq = bind('esk_xtensa_irq_configure', C.c_int, wp, C.POINTER(C.c_uint32)) - total = 0; deliveries = 0; runs = 0; code = binary.read_bytes() + total = 0; deliveries = 0; runs = 0; suspensions = {20: 0, 21: 0}; code = binary.read_bytes() for trial in range(128): sender_cpu = trial // 64; receiver_cpu = 1 - sender_cpu count = 1 + trial % 32; seed = 0x10203040 + trial * 256; replay_trace = None @@ -107,12 +121,13 @@ def bind(name, result, *params): try: ram = fabric(machine); bus = bus_get(machine, 0) assert load(ram, 0, 0x40080000, C.c_char_p(code), len(code)) == 0 + assert bus.write(bus.ctx, 0x3ff00030, 4, 1) == 0 assert configure(machine, sender_cpu, symbols['sender']) == 0 assert configure(machine, receiver_cpu, symbols['idle']) == 0 tx, rx = [core(machine, cpu).contents for cpu in [sender_cpu, receiver_cpu]] tx.ps |= 1 << 16; tx.cpu.a[4] = 0x7ffffff0 tx.cpu.a[0] = 0x12345678; tx.cpu.a[1] = 0x3ffd0000 - tx.cpu.a[6] = 0x3ffb0000; tx.cpu.a[7] = count; tx.cpu.a[8] = seed + tx.cpu.a[6] = 0x3ffb0000; tx.cpu.a[7] = count; tx.cpu.a[8] = seed; tx.cpu.a[9] = int(sender_cpu == 0) # Distinct idle registers must survive every actual ISR. rx.cpu.a[1] = 0x3ffd8000 for r in [0] + list(range(2, 16)): rx.cpu.a[r] = 0xa5000000 + r @@ -133,9 +148,13 @@ def bind(name, result, *params): assert run(machine, sender_cpu, 0) == 0; sender_done = True if cpu == sender_cpu and sender_done: continue + before = (rx.cpu.pc, list(rx.cpu.a)) status = step(machine, cpu, C.byref(event), C.byref(fault)) assert status == 0, (status, cpu, hex(fault.pc), hex(fault.opcode), fault.kind) - assert event.value in [0, 12, 18], event.value + assert event.value in ([0, 12, 18, 20, 21] if args.exercise_controls else [0, 12, 18]), event.value + if event.value in suspensions: + assert cpu == 1 and (rx.cpu.pc, list(rx.cpu.a)) == before + suspensions[event.value] += 1 trace.append((cpu, event.value, tx.cpu.pc, rx.cpu.pc)); total += event.value == 0 if event.value == 12: assert cpu == receiver_cpu and rx.irq.last_pending == 1 << 19 @@ -146,7 +165,8 @@ def bind(name, result, *params): value = C.c_uint32() for addr, expected in [(0x3ffb0000, seed ^ count), (0x3ffb0004, seed ^ count), (0x3ffb0008, count), (0x3ff000dc, 0), - (0x3ff000ec + receiver_cpu * 12, 0), (0x3ffd8f00, 0xc0decafe), (0x3ffd9000, 0xc0decafe)]: + (0x3ff000ec + receiver_cpu * 12, 0), (0x3ff00030, 1), (0x3ff00034, 0), + (0x3ff00038, (seed ^ count) if args.exercise_controls and sender_cpu == 0 else 0), (0x3ffd8f00, 0xc0decafe), (0x3ffd9000, 0xc0decafe)]: assert bus.read(bus.ctx, addr, 4, C.byref(value)) == 0 and value.value == expected, hex(addr) assert tx.cpu.a[6] == seed ^ count and tx.cpu.a[0] == 0x12345678 and tx.cpu.a[1] == 0x3ffd0000 assert tx.base == 0 and tx.start == 1 and rx.base == 0 and rx.start == 1 @@ -158,7 +178,8 @@ def bind(name, result, *params): finally: destroy(machine) assert allocator.live_bytes == 0 - print(f'{runs} compiled mailbox executions, {deliveries} routed interrupts, 128 exact replays (both directions), {total} retired instructions') + if args.exercise_controls: assert all(suspensions.values()) + print(f'{runs} compiled mailbox executions, {deliveries} routed interrupts, 128 exact replays (both directions), {total} retired instructions; suspended turns {suspensions}') if __name__ == '__main__': diff --git a/tests/vendor/xtensa_integer/run_machine_c.py b/tests/vendor/xtensa_integer/run_machine_c.py index 01de178ee..37c194994 100644 --- a/tests/vendor/xtensa_integer/run_machine_c.py +++ b/tests/vendor/xtensa_integer/run_machine_c.py @@ -72,7 +72,7 @@ def bind(name, result, *params): create = bind('esk_esp32_machine_create', C.c_int, C.POINTER(Allocator), C.POINTER(pointer)) destroy = bind('esk_esp32_machine_destroy', None, pointer) fabric = bind('esk_esp32_machine_fabric', pointer, pointer) - bus_get = bind('esk_esp32_fabric_bus', Memory, pointer, C.c_uint) + bus_get = bind('esk_esp32_machine_bus', Memory, pointer, C.c_uint) load = bind('esk_esp32_fabric_load', C.c_int, pointer, C.c_uint, C.c_uint32, pointer, C.c_size_t) configure = bind('esk_esp32_machine_configure', C.c_int, pointer, C.c_uint, C.c_uint32) core = bind('esk_esp32_machine_core', C.POINTER(Windows), pointer, C.c_uint) @@ -89,8 +89,9 @@ def bind(name, result, *params): allocator = Allocator(); alloc_init(C.byref(allocator), None) machine = pointer(); assert create(C.byref(allocator), C.byref(machine)) == 0 try: - ram = fabric(machine); bus = bus_get(ram, 0) + ram = fabric(machine); bus = bus_get(machine, 0) assert load(ram, 0, 0x40080000, C.c_char_p(code), len(code)) == 0 + assert bus.write(bus.ctx, 0x3ff00030, 4, 1) == 0 assert bus.write(bus.ctx, 0x3ffb0000, 4, initial) == 0 stop = 0x7ffffff0; states = [] for cpu in range(2): From fe0fa232050128a1edd8038df795da129200caaf Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 02:52:54 +0000 Subject: [PATCH 17/57] Mask Xtensa PS fields and support atomic status-register exchange --- CHANGELOG.md | 8 +++ core/mcu/xtensa/interrupts.c | 2 +- core/mcu/xtensa/windows.c | 6 +-- core/mcu/xtensa/windows.h | 1 + docs/STATUS.md | 4 ++ docs/engineering/esp32-dual-core.md | 28 +++++++++++ tests/unit/test_xtensa_ps.c | 62 ++++++++++++++++++++++++ tests/vendor/README.md | 5 ++ tests/vendor/xtensa_integer/run_ps.py | 70 +++++++++++++++++++++++++++ 9 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 tests/unit/test_xtensa_ps.c create mode 100644 tests/vendor/xtensa_integer/run_ps.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 77d951003..80d0200bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental Xtensa status-register correctness + +- Fixed PS writes/reads to mask reserved fields and added legal XSR.PS exchange. + RFI restores only implemented PS bits while preserving EPS storage. +- Added 1,304 native checks and 3,456 independent GNU-assembled PS/RFI comparisons, + covering all operand registers, privilege/window priority and atomic interrupt + masking. No full reset, MMU or physical synchronization timing claim is added. + ### Experimental ESP32 APP execution controls - Added DPORT APP clock-enable/run-stall and ROM-consumed boot-address fields. diff --git a/core/mcu/xtensa/interrupts.c b/core/mcu/xtensa/interrupts.c index 63396b8a7..d145dbb81 100644 --- a/core/mcu/xtensa/interrupts.c +++ b/core/mcu/xtensa/interrupts.c @@ -65,7 +65,7 @@ es_status esk_xtensa_irq_poll(struct esk_xtensa_windows *w,unsigned *event) es_status esk_xtensa_irq_return(struct esk_xtensa_windows *w,unsigned level) { if (!w || !w->irq.enabled || level<2 || level>7) return ES_EUNSUPPORTED; - w->ps=w->irq.eps[level];w->cpu.pc=w->irq.epc[level];return ES_OK; + w->ps=w->irq.eps[level]&ESK_XT_PS_MASK;w->cpu.pc=w->irq.epc[level];return ES_OK; } int esk_xtensa_irq_special(unsigned reg) { diff --git a/core/mcu/xtensa/windows.c b/core/mcu/xtensa/windows.c index e2857f828..ab95f83c7 100644 --- a/core/mcu/xtensa/windows.c +++ b/core/mcu/xtensa/windows.c @@ -63,7 +63,7 @@ static uint32_t special_read(const esk_xtensa_windows *w,unsigned reg) switch (reg) { case 0:return w->lbeg; case 1:return w->lend; case 2:return w->lcount; case 3:return w->sar; case 12:return w->scompare1; case 72:return w->base; case 73:return w->start; case 177:return w->epc1; - case 192:return w->depc; case 230:return w->ps; case 232:return w->exccause; default:return w->excvaddr; + case 192:return w->depc; case 230:return w->ps&ESK_XT_PS_MASK; case 232:return w->exccause; default:return w->excvaddr; } } static void special_write(esk_xtensa_windows *w,unsigned reg,uint32_t value) @@ -76,7 +76,7 @@ static void special_write(esk_xtensa_windows *w,unsigned reg,uint32_t value) case 72:w->base=value&15u;load(w);break; case 73:w->start=value&65535u;break; case 177:w->epc1=value;break; case 192:w->depc=value;break; - case 230:w->ps=value;break; case 232:w->exccause=value;break; default:w->excvaddr=value;break; + case 230:w->ps=value&ESK_XT_PS_MASK;break; case 232:w->exccause=value;break; default:w->excvaddr=value;break; } } /* Register references are checked before any access. Zero/compare branches do @@ -149,7 +149,7 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo else if ((word&UINT32_C(0xffff0f))==0x408000) kind=XT_ROTW; else if ((word&UINT32_C(0xff000f))==0x030000 || (word&UINT32_C(0xff000f))==0x130000 || (word&UINT32_C(0xff000f))==0x610000) { unsigned reg=(word>>8)&255u; - if (!special_valid(&next,reg) || (reg==227 && (word&UINT32_C(0xff000f))==0x030000) || ((word&UINT32_C(0xff000f))==0x610000 && reg!=3 && reg!=12)) + if (!special_valid(&next,reg) || (reg==227 && (word&UINT32_C(0xff000f))==0x030000) || ((word&UINT32_C(0xff000f))==0x610000 && reg!=3 && reg!=12 && reg!=230)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); kind=XT_SPECIAL; mask=bit(t); } diff --git a/core/mcu/xtensa/windows.h b/core/mcu/xtensa/windows.h index 160f65df9..ddab4a8f1 100644 --- a/core/mcu/xtensa/windows.h +++ b/core/mcu/xtensa/windows.h @@ -4,6 +4,7 @@ #define ESK_XTENSA_WINDOWS_H #include "mcu/xtensa/execute.h" #include "mcu/xtensa/interrupts.h" +#define ESK_XT_PS_MASK UINT32_C(0x00070fff) #define ESK_XT_PS_EXCM UINT32_C(0x10) #define ESK_XT_PS_WOE UINT32_C(0x40000) /* Experimental LX6 64-AR register-window execution, NOT SoC support. diff --git a/docs/STATUS.md b/docs/STATUS.md index e2379dc6e..0f1babe29 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -80,6 +80,10 @@ comparisons and compiled control/mailbox stress (256 runs, 4,224 interrupts, 128 replays, 551,232 retired instructions). Reset/release remains unsupported; the boot-address register does not bypass missing ROM code or set PC directly. Physical clock/pulse timing, power and cold boot are still unqualified. +PS now masks reserved fields on read/write/exchange and RFI restoration; +XSR.PS preserves its old-value result with privilege/window checks before the +transition. This adds 1,304 native assertions and 3,456 independent assembled +PS/RFI comparisons, including atomic interrupt-mask changes. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 36ac36bfc..dde73e062 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -293,6 +293,34 @@ python tests/vendor/xtensa_integer/run_system_c.py \ --nm /path/to/xtensa-esp32-elf-nm ``` +## Implemented: PS field masking and exchange + +RSR/WSR/XSR.PS expose only implemented fields: INTLEVEL/EXCM/UM/RING/OWB in +bits 11..0 and CALLINC/WOE in bits 18..16 (mask `00070FFF`), as defined by the +Xtensa ISA PS register table. Reserved bits read zero and are discarded on +writes. XSR returns the old PS in its operand register and installs that +register's prior value, with the same field mask. RFI copies only implemented +PS bits from EPS without rewriting EPS storage. + +Existing register-window and privilege checks precede the exchange. Mask changes +are one state transition; a pending interrupt forbidden by both old and new +masks does not enter through an invented intermediate state. Subsequent eligible +polls use the new mask. RING changes are qualified only from EXCM in these +fixtures; full MMU/privilege behavior, synchronization/pipeline latency and a +silicon reset sequence remain outside this subset. + +`test_xtensa_ps` passes 1,304 assertions across all operand registers, reserved +bits, RFI levels 2..7, privilege rejection, window-overflow priority and atomic +mask changes. `run_ps.py` passes 3,456 independently GNU-assembled PS/RFI cases +against a Python register/field oracle. Actual compiled mailbox/control C +handlers continue to pass with the masked PS transitions. + +```sh +python tests/vendor/xtensa_integer/run_ps.py \ + --assembler /path/to/xtensa-esp32-elf-as \ + --objcopy /path/to/xtensa-esp32-elf-objcopy +``` + ## Implemented: ESP32 CPU interrupts and explicit compare-clock ticks `interrupts.c` implements the configured ESP32 LX6 CPU input controller. The diff --git a/tests/unit/test_xtensa_ps.c b/tests/unit/test_xtensa_ps.c new file mode 100644 index 000000000..639c45a03 --- /dev/null +++ b/tests/unit/test_xtensa_ps.c @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/xtensa/windows.h" +static const uint32_t general[3]={0x40081000,0x40081040,0x40081080}; +static const uint32_t high[6]={0x40082000,0x40082040,0x40082080,0x400820c0,0x40082100,0x40082140}; +static es_status execute(esk_xtensa_windows *w,unsigned reg,uint32_t form,unsigned *event) +{ + unsigned char code[3];size_t used=99;esk_xtensa_fault fault;es_status st; + uint32_t word=form|0x00e600u|(reg<<4); + code[0]=(unsigned char)(word&255u);code[1]=(unsigned char)((word>>8)&255u);code[2]=(unsigned char)((word>>16)&255u); + st=esk_xtensa_window_execute(w,NULL,code,3,&used,event,&fault); + CHECK_EQ(used,st==ES_OK && !*event ? 3u : 0u);return st; +} +static void fields_and_exchange(void) +{ + unsigned reg,k,event;const uint32_t samples[]={0,UINT32_MAX,0x18,0x40000,0xfff8f000,0x12345678}; + for(reg=0;reg<16;reg++) for(k=0;k Date: Thu, 5 Nov 2026 03:24:49 +0000 Subject: [PATCH 18/57] Implement bounded ESP32 reset inputs and relocatable exception vectors --- CHANGELOG.md | 17 +- core/mcu/esp32/machine.c | 48 +++-- core/mcu/esp32/machine.h | 28 ++- core/mcu/xtensa/interrupts.c | 3 +- core/mcu/xtensa/windows.c | 42 +++- core/mcu/xtensa/windows.h | 18 +- docs/STATUS.md | 16 +- docs/engineering/esp32-dual-core.md | 29 ++- docs/engineering/esp32-reset-vectors.md | 153 +++++++++++++++ tests/unit/test_esp32_reset.c | 99 ++++++++++ tests/unit/test_xtensa_vectors.c | 91 +++++++++ tests/vendor/README.md | 14 ++ tests/vendor/xtensa_integer/run_controls.py | 6 +- tests/vendor/xtensa_integer/run_reset_c.py | 192 +++++++++++++++++++ tests/vendor/xtensa_integer/run_vectors.py | 115 +++++++++++ tests/vendor/xtensa_integer/run_windows_c.py | 2 +- 16 files changed, 828 insertions(+), 45 deletions(-) create mode 100644 docs/engineering/esp32-reset-vectors.md create mode 100644 tests/unit/test_esp32_reset.c create mode 100644 tests/unit/test_xtensa_vectors.c create mode 100644 tests/vendor/xtensa_integer/run_reset_c.py create mode 100644 tests/vendor/xtensa_integer/run_vectors.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 80d0200bd..35e9895d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental ESP32 CPU reset and vector relocation + +- Added the supported CPU reset subset with explicit static vector selection, + defined PS/LCOUNT/VECBASE fields and declared undefined-state fixture choices. + Aligned RSR/WSR/XSR.VECBASE now relocates window, general and CPU interrupt + entries using original-ESP32 offsets. Low-bit/lock behavior remains unqualified. +- Added opt-in APP reset hold/release at DPORT 0x3ff0002c, commit-safe self-reset, + suspended clocks/deadlines and live-source preservation. Release uses the static + entry; BOOT_ADDR remains firmware-consumed storage. No actual vendor ROM, + complete reset domains, flash/cache or SDK/board boot claim is added. +- Added 2,281 CPU/104 machine assertions, 3,072 assembled register cases, + 960 vendor-offset dispatches, 128 reset-vector and 512 control-field comparisons. + A compiled supervisor/worker plus original test-ROM fixture passes 128 reset + cycles, 16 exact replays and 11,904 retired instructions. + ### Experimental Xtensa status-register correctness - Fixed PS writes/reads to mask reserved fields and added legal XSR.PS exchange. @@ -21,7 +36,7 @@ versions follow semantic versioning. There is no release yet. state, including after a firmware store suspends its own execution. - Added gate-aware supplied-clock/deadline APIs. No automatic clock ticks, physical synchronization/pulse timing or power behavior is claimed. Reset - control remains unsupported; writing the boot address does not set PC or + control requires the separate opt-in reset profile; writing the boot address does not set PC or replace the missing ROM boot sequence. - Added 163 native assertions, 384 pinned-header field comparisons and compiled control/mailbox acceptance: 256 runs, 4,224 interrupts, 128 exact replays and diff --git a/core/mcu/esp32/machine.c b/core/mcu/esp32/machine.c index ef3466ea0..d4e9e49a2 100644 --- a/core/mcu/esp32/machine.c +++ b/core/mcu/esp32/machine.c @@ -6,7 +6,7 @@ struct machine_view { struct esk_esp32_machine *m;unsigned cpu; }; struct esk_esp32_machine { esk_alloc *a;esk_esp32_fabric *fabric; esk_xtensa_windows cpu[2];uint64_t retired[2];unsigned running[2],executing; - uint32_t app_clock,app_stall,app_boot; + uint32_t app_clock,app_stall,app_boot,app_reset,app_reset_enabled,app_reset_vector,app_reset_pending; esk_esp32_matrix matrix;struct machine_view view[2]; }; /* Never mutate an executing CPU from a bus callback. Synchronize the @@ -16,7 +16,7 @@ struct esk_esp32_machine { static void sync_inputs(esk_esp32_machine *m) { unsigned cpu,input;const uint32_t internal=UINT32_C(0x200188c0); - for(cpu=0;cpu<2;cpu++) if(m->cpu[cpu].irq.enabled) { + for(cpu=0;cpu<2;cpu++) if(m->cpu[cpu].irq.enabled && !(cpu==1 && m->app_reset_enabled && m->app_reset)) { uint32_t levels=esk_esp32_matrix_levels(&m->matrix,cpu); for(input=0;input<32;input++) { uint32_t bit=UINT32_C(1)<app_reset_pending) { + (void)esk_xtensa_lx6_reset(&m->cpu[1],m->app_reset_vector);m->app_reset_pending=0; + } +} +/* APP reset input is opt-in. BOOT_ADDR is consumed by firmware, never used + * as the hardware reset PC. Architectural changes occur outside callbacks. */ static es_status control_read(const esk_esp32_machine *m,uint32_t address,unsigned bytes,uint32_t *value) { - if(address<0x3ff00030 || address>0x3ff0003b) return ES_EUNSUPPORTED; + if(address<0x3ff0002c || address>0x3ff0003b) return ES_EUNSUPPORTED; + if(address<0x3ff00030 && !m->app_reset_enabled) return ES_EUNSUPPORTED; if(!value || bytes!=4 || (address&3u)) return ES_EINVAL; - *value=address==0x3ff00030 ? m->app_clock : address==0x3ff00034 ? m->app_stall : m->app_boot; + *value=address==0x3ff0002c ? m->app_reset : address==0x3ff00030 ? m->app_clock : address==0x3ff00034 ? m->app_stall : m->app_boot; return ES_OK; } static es_status control_write(esk_esp32_machine *m,uint32_t address,unsigned bytes,uint32_t value) { - if(address<0x3ff00030 || address>0x3ff0003b) return ES_EUNSUPPORTED; + if(address<0x3ff0002c || address>0x3ff0003b) return ES_EUNSUPPORTED; + if(address<0x3ff00030 && !m->app_reset_enabled) return ES_EUNSUPPORTED; if(bytes!=4 || (address&3u)) return ES_EINVAL; - if(address==0x3ff00030) m->app_clock=value&1u; + if(address==0x3ff0002c) { + if((value&1u) && !m->app_reset) m->app_reset_pending=1; + m->app_reset=value&1u; + } else if(address==0x3ff00030) m->app_clock=value&1u; else if(address==0x3ff00034) m->app_stall=value&1u; else m->app_boot=value; return ES_OK; @@ -54,7 +64,11 @@ static es_status bus_read(void *ctx,uint32_t address,unsigned bytes,uint32_t *va static es_status bus_write(void *ctx,uint32_t address,unsigned bytes,uint32_t value) { struct machine_view *v=(struct machine_view *)ctx;esk_xtensa_memory ram;es_status st; - st=control_write(v->m,address,bytes,value);if(st!=ES_EUNSUPPORTED) return st; + st=control_write(v->m,address,bytes,value); + if(st!=ES_EUNSUPPORTED) { + if(st==ES_OK && !v->m->executing) { apply_reset(v->m);sync_inputs(v->m); } + return st; + } st=esk_esp32_matrix_write(&v->m->matrix,address,bytes,value); if(st!=ES_EUNSUPPORTED) { if(st==ES_OK && !v->m->executing) sync_inputs(v->m); @@ -93,17 +107,23 @@ es_status esk_esp32_machine_nmi_mask(esk_esp32_machine *m,unsigned cpu,int asser es_status st;if(!m || m->executing) return ES_EINVAL; st=esk_esp32_matrix_nmi_mask(&m->matrix,cpu,asserted);if(st==ES_OK) sync_inputs(m);return st; } +es_status esk_esp32_machine_app_reset_configure(esk_esp32_machine *m,unsigned vector_input) +{ + if(!m || vector_input>1 || m->executing) return ES_EINVAL; + m->app_reset_enabled=1;m->app_reset_vector=vector_input;m->app_reset=1;m->app_reset_pending=1; + apply_reset(m);return ES_OK; +} es_status esk_esp32_machine_clock_advance(esk_esp32_machine *m,unsigned cpu,uint64_t ticks) { if(!m || cpu>1 || m->executing) return ES_EINVAL; if(!m->cpu[cpu].irq.enabled) return ES_EUNSUPPORTED; - if(cpu==1 && !m->app_clock) return ES_OK; + if(cpu==1 && (!m->app_clock || (m->app_reset_enabled && m->app_reset))) return ES_OK; return esk_xtensa_clock_advance(&m->cpu[cpu],ticks); } int esk_esp32_machine_clock_deadline(const esk_esp32_machine *m,unsigned cpu,uint64_t *ticks) { if(ticks) *ticks=0; - if(!m || cpu>1 || !ticks || (cpu==1 && !m->app_clock)) return 0; + if(!m || cpu>1 || !ticks || (cpu==1 && (!m->app_clock || (m->app_reset_enabled && m->app_reset)))) return 0; return esk_xtensa_clock_deadline(&m->cpu[cpu],ticks); } es_status esk_esp32_machine_create(esk_alloc *a,esk_esp32_machine **out) @@ -127,6 +147,7 @@ esk_esp32_fabric *esk_esp32_machine_fabric(esk_esp32_machine *m) { return m ? m- es_status esk_esp32_machine_configure(esk_esp32_machine *m,unsigned cpu,uint32_t entry) { if(!m || cpu>1) return ES_EINVAL; + if(cpu==1 && m->app_reset_enabled) return ES_EUNSUPPORTED; esk_xtensa_windows_init(&m->cpu[cpu],entry,NULL);m->running[cpu]=0;m->retired[cpu]=0;return ES_OK; } esk_xtensa_windows *esk_esp32_machine_core(esk_esp32_machine *m,unsigned cpu) { return m && cpu<2 ? &m->cpu[cpu] : NULL; } @@ -142,10 +163,11 @@ es_status esk_esp32_machine_step(esk_esp32_machine *m,unsigned cpu,unsigned *eve if(fault) memset(fault,0,sizeof *fault); if(!m || cpu>1 || !event) return ES_EINVAL; if(!m->running[cpu]) { *event=19;return ES_OK; } + if(cpu==1 && m->app_reset_enabled && m->app_reset) { *event=22;return ES_OK; } if(cpu==1 && (!m->app_clock || m->app_stall)) { *event=!m->app_clock ? 20u : 21u;return ES_OK; } sync_inputs(m);bus=esk_esp32_machine_bus(m,cpu);m->executing=1; st=esk_xtensa_window_step(&m->cpu[cpu],&bus,event,fault); - m->executing=0;sync_inputs(m); + m->executing=0;apply_reset(m);sync_inputs(m); if(st!=ES_OK) m->running[cpu]=0; else if(!*event) m->retired[cpu]++; return st; diff --git a/core/mcu/esp32/machine.h b/core/mcu/esp32/machine.h index aa8123bdf..01fccd3e7 100644 --- a/core/mcu/esp32/machine.h +++ b/core/mcu/esp32/machine.h @@ -8,7 +8,7 @@ typedef struct esk_esp32_machine esk_esp32_machine; /* Experimental two-context execution harness over shared internal memory. * Deterministic serialized instruction turns, not a cycle scheduler or full SoC. - * No DPORT core reset/release, boot ROM, peripheral generators, + * No vendor boot ROM, peripheral generators, * cache arbitration/coherence or board model. Host controls are debug/test * operations, not emulated hardware control registers. No concurrent callers. */ es_status esk_esp32_machine_create(esk_alloc *a,esk_esp32_machine **out); @@ -17,7 +17,7 @@ esk_esp32_fabric *esk_esp32_machine_fabric(esk_esp32_machine *m); /* APP clock enable/stall initialize to documented zero; explicitly enable * 0x3ff00030 before executing APP. 0x3ff00034 stalls/resumes without reset. * 0x3ff00038 is the ROM-consumed boot address, not a direct PC setter. - * Reset register 0x3ff0002c remains unsupported. These functional controls + * Reset register 0x3ff0002c is opt-in through the API below. These functional controls * do not qualify clock-domain synchronizers, retention or physical latency. */ /* Unified data bus includes interrupt-matrix and APP-control MMIO; fetch uses instruction * memory and CAS remains SRAM-only. @@ -29,23 +29,35 @@ esk_xtensa_memory esk_esp32_machine_bus(esk_esp32_machine *m,unsigned cpu); es_status esk_esp32_machine_source(esk_esp32_machine *m,unsigned source,int asserted); es_status esk_esp32_machine_gpio_irq(esk_esp32_machine *m,unsigned cpu,int nmi,int asserted); es_status esk_esp32_machine_nmi_mask(esk_esp32_machine *m,unsigned cpu,int asserted); +/* Opt into the supported APP reset-input profile, asserting reset now. + * Select static vector input 0/1 explicitly; no board strap inference. DPORT + * 0x3ff0002c bit 0 then holds/releases APP. Assertion applies the supported + * CPU reset subset after instruction commit (or immediately for host calls). + * RAM/ROM/remap/matrix/clock/stall/BOOT_ADDR/peer state and diagnostic retired + * totals survive; undefined CPU-register choices are documented in lx6_reset. + * Held reset reports event 22 and suppresses clock/deadline/IRQ delivery. This + * is not power-on/RTC/watchdog reset-domain, actual ROM or silicon timing proof. */ +es_status esk_esp32_machine_app_reset_configure(esk_esp32_machine *m,unsigned vector_input); /* Supply clock ticks through the gate-aware machine boundary. APP ticks are - * dropped while its clock is disabled; enabled-clock ticks still advance the - * functional counter during stall/WAITI/debug pause. No wall-time conversion, + * dropped while its clock is disabled or reset is held. Enabled-clock ticks + * still advance the functional counter during stall/WAITI/debug pause. No wall-time conversion, * synchronizer latency or physical debug-clock policy is implied. Direct - * low-level clock_advance(core) is a debug bypass and does not honor the gate. */ + * low-level clock_advance(core) is a debug bypass for gate/reset inputs. */ es_status esk_esp32_machine_clock_advance(esk_esp32_machine *m,unsigned cpu,uint64_t ticks); int esk_esp32_machine_clock_deadline(const esk_esp32_machine *m,unsigned cpu,uint64_t *ticks); /* Clear one architecture profile, pause it and set explicit entry. Shared * memory, matrix, APP controls and per-core remap survive. - * This is not a silicon reset sequence. */ + * This is not a silicon reset sequence. APP fixture configuration is rejected + * after opting into its reset-input profile; PRO fixture setup stays available. */ es_status esk_esp32_machine_configure(esk_esp32_machine *m,unsigned cpu,uint32_t entry); /* Mutable profile inspection/setup only between execution turns. */ esk_xtensa_windows *esk_esp32_machine_core(esk_esp32_machine *m,unsigned cpu); es_status esk_esp32_machine_run_core(esk_esp32_machine *m,unsigned cpu,int run); -/* Paused core: event 19; APP clock disabled: 20; APP stalled: 21. No fetch +/* Paused core: 19; APP reset held: 22; clock disabled: 20; stalled: 21. No fetch * or interrupt delivery/retirement while suspended. Host pause takes priority - * over clock gate, which takes priority over stall. Functional input latches still update; gated pulse sampling is unqualified. + * over held reset, then clock gate, then stall. Matrix levels remain outside + * the CPU; delivery is suppressed while reset is held. Gated pulse sampling + * remains unqualified. * Fault pauses that core at the failing PC. * Interrupt/window/wait events retain their execution-engine meanings. */ es_status esk_esp32_machine_step(esk_esp32_machine *m,unsigned cpu,unsigned *event,esk_xtensa_fault *fault); diff --git a/core/mcu/xtensa/interrupts.c b/core/mcu/xtensa/interrupts.c index d145dbb81..56d290e29 100644 --- a/core/mcu/xtensa/interrupts.c +++ b/core/mcu/xtensa/interrupts.c @@ -17,6 +17,7 @@ es_status esk_xtensa_irq_configure(struct esk_xtensa_windows *w,const uint32_t h if (!w || !high_vectors || !w->exceptions_enabled) return ES_EINVAL; memset(&w->irq,0,sizeof w->irq);w->irq.enabled=1; for(i=0;i<6;i++) w->irq.vectors[i+2]=high_vectors[i]; + if(w->vecbase_enabled) (void)esk_xtensa_vecbase_configure(w,w->vecbase); return ES_OK; } es_status esk_xtensa_irq_line(struct esk_xtensa_windows *w,unsigned input,int asserted) @@ -56,7 +57,7 @@ es_status esk_xtensa_irq_poll(struct esk_xtensa_windows *w,unsigned *event) if (st!=ES_OK) return st; } else { w->irq.epc[level]=w->cpu.pc;w->irq.eps[level]=w->ps; - w->ps=(w->ps&~15u)|level|ESK_XT_PS_EXCM;w->cpu.pc=w->irq.vectors[level]; + w->ps=(w->ps&~15u)|level|ESK_XT_PS_EXCM;w->cpu.pc=w->vecbase_enabled ? w->vecbase+0x180u+(level-2u)*64u : w->irq.vectors[level]; } if (level==7) w->irq.latched&=~NMI; w->irq.waiting=0;w->irq.last_level=level;w->irq.last_pending=mask; diff --git a/core/mcu/xtensa/windows.c b/core/mcu/xtensa/windows.c index ab95f83c7..3c07bbb7e 100644 --- a/core/mcu/xtensa/windows.c +++ b/core/mcu/xtensa/windows.c @@ -31,12 +31,34 @@ void esk_xtensa_windows_init(esk_xtensa_windows *w, uint32_t pc, const uint32_t memset(w,0,sizeof *w); w->cpu.pc=pc; w->ps=ESK_XT_PS_WOE; w->start=1; if (vectors) memcpy(w->vectors,vectors,sizeof w->vectors); } +es_status esk_xtensa_vecbase_configure(esk_xtensa_windows *w,uint32_t base) +{ + unsigned i;static const uint32_t general[3]={0x300,0x340,0x3c0}; + if(!w) return ES_EINVAL; + if(base&1023u) return ES_EUNSUPPORTED; + w->vecbase=base;w->vecbase_enabled=1; + for(i=0;i<6;i++) { w->vectors[i]=base+i*64u;w->irq.vectors[i+2]=base+0x180u+i*64u; } + for(i=0;i<3;i++) w->exception_vectors[i]=base+general[i]; + return ES_OK; +} +es_status esk_xtensa_lx6_reset(esk_xtensa_windows *w,unsigned vector_input) +{ + if(!w || vector_input>1) return ES_EINVAL; + save(w);w->base&=15u;w->start&=65535u; + w->ps=0x1f;w->lcount=0;w->cpu.pc=vector_input ? 0x40000400u : 0x50000000u; + w->exceptions_enabled=1;w->irq.enabled=1;w->irq.waiting=0; + /* INTERRUPT is undefined at reset. Choose no pending requests; the SoC + * provider re-drives its live levels after releasing reset. */ + w->irq.lines=w->irq.latched=w->irq.last_level=w->irq.last_pending=0; + return esk_xtensa_vecbase_configure(w,0x40000000); +} void esk_xtensa_exception_configure(esk_xtensa_windows *w, const uint32_t vectors[3]) { if (!w) return; w->exceptions_enabled=vectors ? 1u : 0u; if (!vectors) { w->irq.enabled=0; w->irq.waiting=0; } if (vectors) memcpy(w->exception_vectors,vectors,sizeof w->exception_vectors); + if(w->vecbase_enabled) (void)esk_xtensa_vecbase_configure(w,w->vecbase); } es_status esk_xtensa_raise_exception(esk_xtensa_windows *w, unsigned cause, uint32_t address, int has_address, unsigned *event) @@ -49,13 +71,14 @@ es_status esk_xtensa_raise_exception(esk_xtensa_windows *w, unsigned cause, else { w->epc1=w->cpu.pc; index=(w->ps&32u) ? 1u : 0u; } w->exccause=cause; if (has_address) w->excvaddr=address; - w->ps|=ESK_XT_PS_EXCM; w->cpu.pc=w->exception_vectors[index]; + w->ps|=ESK_XT_PS_EXCM; + w->cpu.pc=w->vecbase_enabled ? w->vecbase+(index==2 ? 0x3c0u : 0x300u+index*64u) : w->exception_vectors[index]; *event=7u+index; return ES_OK; } static int special_valid(const esk_xtensa_windows *w,unsigned reg) { - return (w->irq.enabled && esk_xtensa_irq_special(reg)) || reg<=3 || reg==12 || reg==72 || reg==73 || reg==177 || reg==192 || reg==230 || reg==232 || reg==238; + return (w->irq.enabled && esk_xtensa_irq_special(reg)) || reg<=3 || reg==12 || reg==72 || reg==73 || reg==177 || reg==192 || reg==230 || (reg==231 && w->vecbase_enabled) || reg==232 || reg==238; } static uint32_t special_read(const esk_xtensa_windows *w,unsigned reg) { @@ -63,7 +86,7 @@ static uint32_t special_read(const esk_xtensa_windows *w,unsigned reg) switch (reg) { case 0:return w->lbeg; case 1:return w->lend; case 2:return w->lcount; case 3:return w->sar; case 12:return w->scompare1; case 72:return w->base; case 73:return w->start; case 177:return w->epc1; - case 192:return w->depc; case 230:return w->ps&ESK_XT_PS_MASK; case 232:return w->exccause; default:return w->excvaddr; + case 192:return w->depc; case 230:return w->ps&ESK_XT_PS_MASK; case 231:return w->vecbase; case 232:return w->exccause; default:return w->excvaddr; } } static void special_write(esk_xtensa_windows *w,unsigned reg,uint32_t value) @@ -76,7 +99,7 @@ static void special_write(esk_xtensa_windows *w,unsigned reg,uint32_t value) case 72:w->base=value&15u;load(w);break; case 73:w->start=value&65535u;break; case 177:w->epc1=value;break; case 192:w->depc=value;break; - case 230:w->ps=value&ESK_XT_PS_MASK;break; case 232:w->exccause=value;break; default:w->excvaddr=value;break; + case 230:w->ps=value&ESK_XT_PS_MASK;break; case 231:(void)esk_xtensa_vecbase_configure(w,value);break; case 232:w->exccause=value;break; default:w->excvaddr=value;break; } } /* Register references are checked before any access. Zero/compare branches do @@ -112,7 +135,7 @@ static unsigned overflow(esk_xtensa_windows *w, uint32_t mask) event=(w->start&bit(victim+1)) ? 1u : (w->start&bit(victim+2)) ? 3u : 5u; w->epc1=w->cpu.pc; w->ps=(w->ps&~UINT32_C(0xf00)) | (w->base<<8) | ESK_XT_PS_EXCM; - save(w); w->base=victim; load(w); w->cpu.pc=w->vectors[event-1]; + save(w); w->base=victim; load(w); w->cpu.pc=w->vecbase_enabled ? w->vecbase+(event-1u)*64u : w->vectors[event-1]; return event; } es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memory *memory, @@ -149,7 +172,7 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo else if ((word&UINT32_C(0xffff0f))==0x408000) kind=XT_ROTW; else if ((word&UINT32_C(0xff000f))==0x030000 || (word&UINT32_C(0xff000f))==0x130000 || (word&UINT32_C(0xff000f))==0x610000) { unsigned reg=(word>>8)&255u; - if (!special_valid(&next,reg) || (reg==227 && (word&UINT32_C(0xff000f))==0x030000) || ((word&UINT32_C(0xff000f))==0x610000 && reg!=3 && reg!=12 && reg!=230)) + if (!special_valid(&next,reg) || (reg==227 && (word&UINT32_C(0xff000f))==0x030000) || ((word&UINT32_C(0xff000f))==0x610000 && reg!=3 && reg!=12 && reg!=230 && reg!=231)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); kind=XT_SPECIAL; mask=bit(t); } @@ -202,7 +225,7 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo if (next.start&bit(next.base)) { next.start&=~bit(old); load(&next); next.cpu.pc=return_pc; } else { next.epc1=pc; next.ps=(next.ps&~UINT32_C(0xf00))|(old<<8)|ESK_XT_PS_EXCM; - load(&next); e=n*2; next.cpu.pc=next.vectors[e-1]; *w=next; *event=e; return ES_OK; + load(&next); e=n*2; next.cpu.pc=next.vecbase_enabled ? next.vecbase+(e-1u)*64u : next.vectors[e-1]; *w=next; *event=e; return ES_OK; } flow=0; /* CALLINC is preserved; the ISA permits implementation-dependent update. */ @@ -217,7 +240,10 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo unsigned reg=(word>>8)&255u; uint32_t value=next.cpu.a[t],old=special_read(&next,reg); uint32_t form=word&UINT32_C(0xff000f); if (form==0x030000) next.cpu.a[t]=old; - else { special_write(&next,reg,value); if (form==0x610000) next.cpu.a[t]=old; } + else { + if(reg==231 && (value&1023u)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + special_write(&next,reg,value);if(form==0x610000) next.cpu.a[t]=old; + } next.cpu.pc=pc+3; } else if (kind==XT_LOOP) { unsigned condition=(word>>12)&15u; uint32_t count=next.cpu.a[s]; diff --git a/core/mcu/xtensa/windows.h b/core/mcu/xtensa/windows.h index ddab4a8f1..7693573ac 100644 --- a/core/mcu/xtensa/windows.h +++ b/core/mcu/xtensa/windows.h @@ -10,7 +10,8 @@ /* Experimental LX6 64-AR register-window execution, NOT SoC support. * cpu.a is the editable visible cache; physical ARs synchronize on successful * instructions/exception transitions. Init configures an ABI test state, not - * silicon power-on defaults. Vectors are caller-supplied, not inferred. + * silicon power-on defaults. Legacy vectors are caller-supplied; explicit + * reset/relocation profiles use pinned original-ESP32 vector configuration. * Selected special registers, SAR and opt-in XEA2-with-DEPC exception entry * are implemented. CPU interrupt/timer delivery is opt-in; the SoC interrupt * matrix, automatic bus/MMU fault dispatch, complete ISA, MMU/caches and @@ -23,6 +24,7 @@ typedef struct esk_xtensa_windows { uint32_t exception_vectors[3]; /* kernel, user, double; configured explicitly */ esk_xtensa_interrupts irq; uint32_t vectors[6]; /* overflow4, underflow4, overflow8, underflow8, overflow12, underflow12 */ + uint32_t vecbase,vecbase_enabled; } esk_xtensa_windows; /* event 0: retired; 1..6: window transition; 7/8/9: kernel/user/double exception; * 11..17: accepted interrupt level 1..7; 18: suspended WAITI (step only). @@ -30,6 +32,20 @@ typedef struct esk_xtensa_windows { * syscall/illegal handlers explicitly decide whether to advance EPC1/DEPC. * Architectural exception entries intentionally update saved state and PC. * Other failures retain state and return the ordinary fault report. */ +/* Explicit ESP32 relocatable-vector profile. Only 1024-aligned bases are + * qualified; unaligned/lock-bit writes fail unchanged rather than guessing + * silicon masking. Aligned RSR/WSR/XSR.VECBASE use SR231 when enabled. + * This sets locations, not exception/IRQ enables. Legacy fixture vectors stay + * in effect until explicitly enabled; while active, VECBASE owns locations. */ +es_status esk_xtensa_vecbase_configure(esk_xtensa_windows *state,uint32_t base); +/* Reset the supported original-ESP32 CPU subset, on an initialized profile. + * Explicit reset-vector input: 0=0x50000000, 1=0x40000400. PS=0x1f, + * LCOUNT=0, VECBASE=0x40000000; exception/IRQ profiles enabled and WAITI + * canceled. Undefined architectural registers retain fixture values; pending + * IRQ state is chosen zero and external levels must be re-driven. These are + * deterministic choices, NOT silicon retention guarantees. Missing ISA/debug/ + * MMU/cache reset state, pin selection and power/reset timing stay unqualified. */ +es_status esk_xtensa_lx6_reset(esk_xtensa_windows *state,unsigned vector_input); /* Explicit exception profile; no SoC vector addresses inferred. Unknown or * unimplemented instructions remain unsupported, never silently illegal. */ void esk_xtensa_exception_configure(esk_xtensa_windows *state, const uint32_t vectors[3]); diff --git a/docs/STATUS.md b/docs/STATUS.md index 0f1babe29..a48876a91 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -55,7 +55,7 @@ two-context harness shares this RAM while keeping CPU/interrupt/clock state private. Native fixtures cover address/permission/remap/fault/wait boundaries; 512 actual ESP32 compiled task executions and 128 replay comparisons pass through the native bus (229,322 instructions). This remains a bounded memory/execution -harness: cache/MMU, DPORT core reset/release controls, complete SMP and SoC boot are absent. +harness: cache/MMU, complete reset domains, full SMP and SoC boot are absent. L32AI/S32RI now provide acquire/release ordering under the synchronous completed callback contract (30,732 native assertions). The compiled shared-bus acceptance includes acquire/CAS/release spinlocks protecting ordinary writes. Asynchronous @@ -68,8 +68,7 @@ instruction commit, including paused-peer edges. New fixtures pass 64,374 matrix and 103 integration assertions; pinned-header referees pass 4,288 routes and 512 combined-source/mask cases. Actual ESP32 C sender/C handler acceptance with an assembled entry/return wrapper passes 256 mailbox executions, 4,224 interrupts and 128 exact replays (512,128 retired -instructions), in both directions. Peripheral generators, PID hardware, DPORT -reset/release, APB timing, +instructions), in both directions. Peripheral generators, PID hardware, complete reset domains, APB timing, flash/cache and boot remain absent; no complete ISR/RTOS or timing claim follows. APP clock/stall fields and the ROM-consumed boot-address register now work through the machine bus. APP starts clock-disabled; fixture setup explicitly @@ -77,13 +76,22 @@ enables it. Gate/stall stops fetch/retirement and resumes at the same PC, withou resetting shared state. Gate-aware supplied-clock/deadline APIs suppress APP counter ticks while gated. New evidence: 163 native assertions, 384 vendor-field comparisons and compiled control/mailbox stress (256 runs, 4,224 interrupts, -128 replays, 551,232 retired instructions). Reset/release remains unsupported; +128 replays, 551,232 retired instructions). APP reset/release requires the separate opt-in supported reset profile below; the boot-address register does not bypass missing ROM code or set PC directly. Physical clock/pulse timing, power and cold boot are still unqualified. PS now masks reserved fields on read/write/exchange and RFI restoration; XSR.PS preserves its old-value result with privilege/window checks before the transition. This adds 1,304 native assertions and 3,456 independent assembled PS/RFI comparisons, including atomic interrupt-mask changes. +A [bounded reset/vector profile](engineering/esp32-reset-vectors.md) now supports +explicit static reset-input selection, PS/LCOUNT/VECBASE reset fields and aligned +VECBASE relocation with fixed vendor offsets. Undefined CPU register values use +an explicit fixture policy, not a silicon retention claim. Opt-in APP reset input +holds/releases the core after instruction commit and preserves shared source +state. Evidence: 2,281 CPU/104 machine assertions, 3,072 assembled VECBASE accesses, +960 vendor-offset dispatches and 128 reset-address comparisons. Original test-ROM +handoff passes 128 compiled supervisor/worker cycles and 16 exact replays. +Actual Espressif ROM, full reset-domain behavior and SDK/board boot remain absent. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index dde73e062..2d2795c25 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -1,6 +1,6 @@ # Original ESP32 dual-core target: identity and implementation gates -**Status: firmware inspection, experimental Xtensa execution, shared internal RAM, interrupt routing and APP execution controls implemented; no runnable board target is added.** Requested by +**Status: firmware inspection, experimental Xtensa execution, shared internal RAM, interrupt routing, APP controls and bounded reset/vector profiles implemented; no runnable board target is added.** Requested by the owner on 2026-11-04. This is the original ESP32 family, not ESP32-C3, ESP32-S2 or ESP32-S3. The generic marketplace title “ESP-32 / ESP-32S WiFi+Bluetooth Dual Cores” does not identify a reproducible board revision. @@ -544,7 +544,7 @@ cannot intervene. This is not host-thread-safe memory, cache/coherence, arbitration, an instruction-to-cycle model, DPORT reset/release, physical clock timing, retention, DMA or a complete SMP machine. Reconfiguring one profile preserves shared RAM and remap settings and is explicitly not a silicon reset sequence. No CPU -constructor, board picker entry, boot ROM execution or ESP-IDF boot is added. +constructor, board picker entry, Espressif ROM execution or ESP-IDF boot is added. Native fixtures pass 360,531 memory assertions and 86 machine assertions: every SRAM1 word and byte lane, remap isolation, RTC @@ -691,10 +691,10 @@ that does not honor machine gates. No ticks are inferred from instructions. Pending requests remain pending while suspended and can enter on resume. The boot-address register stores all 32 bits and never directly changes PC. -The TRM explicitly says ROM code jumps to this address after APP boots. That ROM -path is not implemented. In particular, APPCPU_CTRL_A reset/release at -`3FF0002C` remains unsupported; clock enable alone is not a real cold-boot -sequence. No synthetic reset state, ROM jump, reset cause/retention domain, +The TRM explicitly says ROM code jumps to this address after APP boots. Actual +Espressif ROM execution is not implemented. APPCPU_CTRL_A reset/release at +`3FF0002C` now requires the separate opt-in supported reset profile below; +clock enable alone is not a real cold-boot sequence. No synthetic reset state, ROM jump, reset cause/retention domain, oscillator/PLL/APB conversion, power consumption or complete board boot is added. Proof: @@ -727,6 +727,23 @@ python tests/vendor/xtensa_integer/run_ipi_c.py \ --exercise-controls ``` +## Implemented: supported reset and relocatable-vector profiles + +The [reset/vector contract and reproduction](esp32-reset-vectors.md) describe +explicit static reset-input selection, defined PS/LCOUNT/VECBASE state, +undefined-register fixture policy, aligned SR231 read/write/exchange and dynamic +exception/interrupt/window entry. APP reset hold/release is an opt-in DPORT +profile with commit-safe self-reset, clock/deadline suppression, peripheral +source preservation and no boot-address PC shortcut. Legacy debug fixtures +retain their existing contract until reset is enabled. + +Evidence includes 2,281 CPU and 104 machine assertions, 3,072 independent +assembled VECBASE accesses, 960 vendor-offset dispatches, 128 reset-address +comparisons and 512 vendor-control-field comparisons. A compiled supervisor and +worker pass 128 reset cycles through an original temporary test-ROM program +(11,904 retired instructions, 16 exact replays). This is not Espressif ROM, +complete silicon reset, MMU/cache or real SDK/board boot qualification. + ## Implemented: standalone firmware integrity inspection `embsim image-info esp32 app.bin` inspects a plaintext original-ESP32 image diff --git a/docs/engineering/esp32-reset-vectors.md b/docs/engineering/esp32-reset-vectors.md new file mode 100644 index 000000000..a89abb26f --- /dev/null +++ b/docs/engineering/esp32-reset-vectors.md @@ -0,0 +1,153 @@ +# Original ESP32: supported CPU reset and vector profiles + +This is a bounded extension of the [experimental ESP32 foundation](esp32-dual-core.md), +not a registered board target or a complete silicon reset model. It separates +static reset entry, relocatable exception locations and firmware's boot-address +handoff. No Espressif ROM or ROM implementation is bundled. + +## References and supported state + +Reset addresses and exception offsets come from the actual Espressif ESP32 +GCC 8.4.0 esp-2021r2-patch5 `xtensa/config/core-isa.h`: +SHA-256 `60d20f76c7730b24041b9f55b16e0062f30bf85c7661c7ed215f050fedcdb344`. +The main foundation document records its archive URL/hash. The Xtensa ISA PS, +loop, relocatable-vector and interrupt register definitions distinguish defined +reset fields from undefined state. + +TRM v5.9 chapter 12 and the pinned ESP-IDF v4.4.7 +[dport_reg.h](https://github.com/espressif/esp-idf/blob/v4.4.7/components/soc/esp32/include/soc/dport_reg.h) +define the APP reset-input register at `3FF0002C`, bit 0, initial field 1. +[ESP-IDF's CPU startup](https://github.com/espressif/esp-idf/blob/v4.4.7/components/esp_system/port/cpu_start.c) +uses clock/stall/reset controls; its +[linker vector layout](https://github.com/espressif/esp-idf/blob/v4.4.7/components/esp_system/ld/esp32/sections.ld.in) +places handlers at the configured dynamic offsets. These are references, not +vendor startup code executed by the acceptance fixture below. + +`esk_xtensa_lx6_reset` operates on an initialized CPU profile: + +| State | Supported reset behavior | +| --- | --- | +| Static reset input 0 | PC `50000000` | +| Static reset input 1 | PC `40000400`, the configured default address | +| PS | `0000001F`: EXCM set, interrupt mask 15, window-overflow enable clear | +| LCOUNT | 0 | +| VECBASE | `40000000`, relocatable-vector profile enabled | +| Exception/CPU interrupt profiles | Enabled for supported operations | +| WAITI and last-delivery metadata | Cleared | +| Pending CPU requests | Chosen zero; the provider must re-drive external levels | +| Undefined architectural registers | Prior initialized fixture values preserved | + +Preserving undefined registers is a deterministic fixture policy, not a claim +that silicon retains them. It covers ARs/window state, SAR/SCOMPARE1, loop +addresses, exception context, INTENABLE and counter/compare values. INTERRUPT +is architecturally undefined after reset; choosing no pending requests is also +an explicit policy. Firmware must initialize states it depends on. No reset +values are fabricated for unimplemented debug, FPU, MMU/cache, MEMCTL, security +or other state. Invalid reset-input selections fail before changing the profile. + +## Relocatable exception locations + +`esk_xtensa_vecbase_configure` enables the explicit ESP32 vector profile. +RSR/WSR/XSR.VECBASE use privileged SR231. The supported interface accepts +1024-aligned bases only. Other writes fail unchanged with an unsupported +operation diagnostic: low-bit masking and optional lock-bit behavior have not +been silicon-qualified. This alignment restriction is a functional profile, +not a measured claim about those silicon bits. + +| Dynamic group | Offsets from VECBASE | +| --- | --- | +| Window overflow/underflow 4 | `000`, `040` | +| Window overflow/underflow 8 | `080`, `0C0` | +| Window overflow/underflow 12 | `100`, `140` | +| Interrupt levels 2..5 | `180`, `1C0`, `200`, `240` | +| Debug/level-6 location | `280`; debug exception delivery remains unsupported | +| NMI/level 7 | `2C0` | +| Kernel, user, double exception | `300`, `340`, `3C0` | + +Dynamic entry uses VECBASE plus the pinned offset. The static reset addresses +do not move with VECBASE. Configuring locations does not itself enable exception +or interrupt delivery. Legacy ABI fixtures can still use caller-supplied vectors +until this mode is enabled. Once enabled, VECBASE is authoritative; legacy +configuration APIs cannot substitute arbitrary locations. Cached vector arrays +are refreshed for inspection, and entry derives its address from the active base. +Register-window and privilege checks precede a register exchange or rejection. +No pipeline, RSYNC latency, MMU translation or automatic bus-fault dispatch is +inferred from successful relocation. + +## APP reset hold and release + +`esk_esp32_machine_app_reset_configure(machine, vector_input)` explicitly enables +the reset-input profile and asserts APP reset. The caller supplies reset-vector +input 0 or 1; no board strap, RTC wake or reset-cause selection is inferred. +Without opting in, the legacy debug machine keeps the reset register unsupported. + +In this profile DPORT `3FF0002C` bit 0 holds/releases APP. Assertion applies the +supported CPU reset state at an instruction boundary; a self-resetting store +commits first. A host write between turns applies immediately. Repeated writes +of an already asserted bit do not create another transition. This does not +qualify hardware assertion/deassertion phase or synchronizer latency. + +| Execution condition | Machine event | +| --- | --- | +| Host debug pause | 19 | +| APP held in reset | 22 | +| APP clock disabled | 20 | +| APP run-stalled | 21 | + +The table is in priority order. Held reset prevents fetch, retirement, interrupt +entry and gate-aware supplied-clock advancement; its compare deadline is absent. +Matrix source levels remain outside the CPU and are re-driven on release. A +held peripheral request is not acknowledged by resetting its destination CPU. +CPU-only reset preserves the peer, RAM, supplied ROM, remap/matrix/control +registers and diagnostic retirement totals within this model. Global/RTC/watchdog, +power-on and retention-domain behavior remain separate unimplemented contracts. + +Release does not override a debug/fault pause, enable the clock or clear stall. +It does not use BOOT_ADDR as PC. ROM code must fetch from the selected static +reset entry and perform any handoff. A missing ROM causes a fetch diagnostic at +the static address even when BOOT_ADDR points to valid executable RAM. Existing +`machine_configure` remains a debug fixture operation, not a reset/boot sequence. +APP fixture configuration is rejected after opting into its reset-input profile, +so it cannot silently replace the new profile with an ABI fixture. PRO fixture +setup remains available for a supervisor. + +## Proof and reproduction + +- `test_xtensa_vectors`: 2,281 assertions for defined reset fields, undefined + fixture policy, all register operands, rejected unaligned writes, all six + window entries, general exceptions and supported CPU interrupt levels. +- `test_esp32_reset`: 104 assertions for hold/release, self-reset commit, + pause/clock/stall priority, missing-ROM rejection, live-source preservation, + alternate static entry and remap isolation. +- `run_vectors.py`: 3,072 GNU-assembled VECBASE accesses, 960 dispatches using + parsed vendor offsets and 128 static reset comparisons using the pinned header. +- `run_controls.py --include-reset`: 512 register-field comparisons through both + CPU bus views using parsed vendor addresses, widths and defaults. +- `run_reset_c.py`: 128 compiled supervisor/worker reset cycles through an + original test-ROM program, 128 static ROM entries, 16 exact replays and 11,904 + retired instructions. The fixture initializes window state/INTENABLE/PS and + VECBASE, reads BOOT_ADDR in firmware and calls the worker through emulated + instructions. It checks stack guards, cumulative shared state, a peripheral + source surviving reset and caller/window state. Its 2,448 held-reset turns + are instruction attempts, not physical cycles. + +The original test ROM is compiled into a temporary image and supplied through +the external ROM API. It is neither Espressif ROM nor evidence of real ROM, +ESP-IDF/Arduino boot, flash/cache, full reset coverage or board support. There +are no Python memory callbacks or host PC overrides in its APP handoff. + +```sh +python tests/vendor/xtensa_integer/run_vectors.py \ + --core-isa /path/to/xtensa-esp32-elf/include/xtensa/config/core-isa.h \ + --assembler /path/to/xtensa-esp32-elf-as \ + --objcopy /path/to/xtensa-esp32-elf-objcopy +python tests/vendor/xtensa_integer/run_controls.py \ + --dport-reg /path/to/esp-idf/components/soc/esp32/include/soc/dport_reg.h \ + --soc-header /path/to/esp-idf/components/soc/esp32/include/soc/soc.h \ + --include-reset +python tests/vendor/xtensa_integer/run_reset_c.py \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld \ + --nm /path/to/xtensa-esp32-elf-nm +``` diff --git a/tests/unit/test_esp32_reset.c b/tests/unit/test_esp32_reset.c new file mode 100644 index 000000000..4a13de3f7 --- /dev/null +++ b/tests/unit/test_esp32_reset.c @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/machine.h" +static void hold_release_and_no_boot_shortcut(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_xtensa_memory bus;esk_xtensa_windows *app,*pro,before; + unsigned event;esk_xtensa_fault fault;uint32_t v;uint64_t ticks=99; + unsigned char nop[]={0x3d,0xf0}; + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK);bus=esk_esp32_machine_bus(m,0); + app=esk_esp32_machine_core(m,1);pro=esk_esp32_machine_core(m,0);before=*app; + CHECK_EQ(esk_esp32_machine_app_reset_configure(m,2),ES_EINVAL);CHECK_EQ(memcmp(app,&before,sizeof before),0); + CHECK_EQ(bus.read(bus.ctx,0x3ff0002c,4,&v),ES_EUNSUPPORTED); + pro->cpu.a[5]=99;before=*pro;app->cpu.a[6]=0x1234;app->irq.ccount=987;app->lcount=9; + CHECK_EQ(bus.write(bus.ctx,0x3ff00038,4,0x40080000),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff00034,4,1),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ffb0000,4,0xdeadbeef),ES_OK); + CHECK_EQ(esk_esp32_fabric_load(esk_esp32_machine_fabric(m),1,0x40080000,nop,sizeof nop),ES_OK); + CHECK_EQ(esk_esp32_machine_app_reset_configure(m,1),ES_OK); + CHECK_EQ(app->cpu.pc,0x40000400);CHECK_EQ(app->ps,0x1f);CHECK_EQ(app->lcount,0);CHECK_EQ(app->irq.ccount,987); + CHECK_EQ(app->cpu.a[6],0x1234);CHECK_EQ(memcmp(pro,&before,sizeof before),0); + before=*app;CHECK_EQ(esk_esp32_machine_configure(m,1,0x40080000),ES_EUNSUPPORTED); + CHECK_EQ(memcmp(app,&before,sizeof before),0); + CHECK_EQ(bus.read(bus.ctx,0x3ff0002c,4,&v),ES_OK);CHECK_EQ(v,1); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,19); + CHECK_EQ(esk_esp32_machine_run_core(m,1,1),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,22); + CHECK_EQ(esk_esp32_machine_clock_advance(m,1,100),ES_OK);CHECK_EQ(app->irq.ccount,987); + CHECK_EQ(esk_esp32_machine_clock_deadline(m,1,&ticks),0);CHECK_EQ(ticks,0); + CHECK_EQ(bus.write(bus.ctx,0x3ff0002c,4,2),ES_OK); /* low bit releases */ + CHECK_EQ(bus.read(bus.ctx,0x3ff0002c,4,&v),ES_OK);CHECK_EQ(v,0); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,20); + CHECK_EQ(bus.write(bus.ctx,0x3ff00030,4,1),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,21); + CHECK_EQ(bus.write(bus.ctx,0x3ff00034,4,0),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_EINVAL);CHECK_EQ(fault.kind,ESK_XT_FETCH); + CHECK_EQ(fault.address,0x40000400);CHECK_EQ(app->cpu.pc,0x40000400);CHECK_EQ(esk_esp32_machine_retired(m,1),0); + CHECK_EQ(bus.read(bus.ctx,0x3ff00038,4,&v),ES_OK);CHECK_EQ(v,0x40080000); + CHECK_EQ(bus.read(bus.ctx,0x3ffb0000,4,&v),ES_OK);CHECK_EQ(v,0xdeadbeefu); + CHECK_EQ(bus.write(bus.ctx,0x3ff0002c,1,1),ES_EINVAL); + CHECK_EQ(bus.write(bus.ctx,0x3ff0002d,4,1),ES_EINVAL); + CHECK_EQ(bus.write(bus.ctx,0x3ff0002c,4,UINT32_MAX),ES_OK); + CHECK_EQ(bus.read(bus.ctx,0x3ff0002c,4,&v),ES_OK);CHECK_EQ(v,1); + CHECK_EQ(bus.write(bus.ctx,0x3ff0002c,4,0),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,19); /* fault pause survives reset */ + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +static void self_reset_and_live_source(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_xtensa_memory bus;esk_xtensa_windows *app; + unsigned char *rom=(unsigned char *)calloc(0x60000,1);unsigned event;esk_xtensa_fault fault;uint32_t v; + CHECK(rom!=NULL);if(!rom) return; + /* Original fixture ROM, not Espressif ROM: NOP.N; S32I A2,A3,0. */ + rom[0x400]=0x3d;rom[0x401]=0xf0;rom[0x402]=0x22;rom[0x403]=0x63; + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK);bus=esk_esp32_machine_bus(m,0); + CHECK_EQ(esk_esp32_fabric_rom(esk_esp32_machine_fabric(m),0,rom,0x60000),ES_OK);free(rom); + CHECK_EQ(esk_esp32_machine_app_reset_configure(m,1),ES_OK);app=esk_esp32_machine_core(m,1); + CHECK_EQ(bus.write(bus.ctx,0x3ff00030,4,1),ES_OK);CHECK_EQ(bus.write(bus.ctx,0x3ff0002c,4,0),ES_OK); + CHECK_EQ(esk_esp32_machine_run_core(m,1,1),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(app->cpu.pc,0x40000402); + app->cpu.a[2]=1;app->cpu.a[3]=0x3ff0002c; + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,0); + CHECK_EQ(app->cpu.pc,0x40000400);CHECK_EQ(app->ps,0x1f);CHECK_EQ(esk_esp32_machine_retired(m,1),2); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,22); + /* CPU reset cannot acknowledge or reset the peripheral/matrix source. */ + CHECK_EQ(bus.write(bus.ctx,0x3ff002a0,4,19),ES_OK); + CHECK_EQ(esk_esp32_machine_source(m,34,1),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(app),0); + CHECK_EQ(bus.read(bus.ctx,0x3ff000fc,4,&v),ES_OK);CHECK_EQ(v,4); + CHECK_EQ(bus.write(bus.ctx,0x3ff0002c,4,0),ES_OK);CHECK_EQ(esk_xtensa_irq_pending(app),1u<<19); + app->irq.intenable=1u<<19; + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,0); /* PS reset mask blocks level 2 */ + app->ps=ESK_XT_PS_WOE; + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(event,12);CHECK_EQ(app->cpu.pc,0x40000180); + CHECK_EQ(app->irq.epc[2],0x40000402);CHECK_EQ(esk_esp32_machine_retired(m,1),3); + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +static void alternate_static_vector_and_remap(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_xtensa_memory bus;esk_xtensa_windows *app; + unsigned event;esk_xtensa_fault fault;unsigned char nop[]={0x3d,0xf0};uint32_t v; + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK);bus=esk_esp32_machine_bus(m,0); + CHECK_EQ(esk_esp32_fabric_load(esk_esp32_machine_fabric(m),1,0x50000000,nop,sizeof nop),ES_OK); + CHECK_EQ(esk_esp32_machine_app_reset_configure(m,0),ES_OK);app=esk_esp32_machine_core(m,1); + CHECK_EQ(bus.write(bus.ctx,0x3ff00030,4,1),ES_OK);CHECK_EQ(bus.write(bus.ctx,0x3ff0002c,4,0),ES_OK); + CHECK_EQ(esk_esp32_machine_run_core(m,1,1),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(app->cpu.pc,0x50000002); + CHECK_EQ(app->vecbase,0x40000000); /* static selector does not relocate dynamic vectors */ + CHECK_EQ(esk_esp32_fabric_remap(esk_esp32_machine_fabric(m),1,1),ES_OK); + CHECK_EQ(esk_esp32_fabric_load(esk_esp32_machine_fabric(m),1,0x40000400,nop,sizeof nop),ES_OK); + CHECK_EQ(esk_esp32_machine_app_reset_configure(m,1),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff0002c,4,0),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,1,&event,&fault),ES_OK);CHECK_EQ(app->cpu.pc,0x40000402); + CHECK_EQ(esk_esp32_machine_retired(m,1),2); + CHECK_EQ(bus.fetch(bus.ctx,0x40000400,1,&v),ES_EUNSUPPORTED); /* peer remap did not change PRO view */ + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +TEST_MAIN_BEGIN + (void)t_str_eq;RUN(hold_release_and_no_boot_shortcut);RUN(self_reset_and_live_source);RUN(alternate_static_vector_and_remap); +TEST_MAIN_END diff --git a/tests/unit/test_xtensa_vectors.c b/tests/unit/test_xtensa_vectors.c new file mode 100644 index 000000000..53f9659a1 --- /dev/null +++ b/tests/unit/test_xtensa_vectors.c @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/xtensa/windows.h" +static es_status execute(esk_xtensa_windows *w,uint32_t word,unsigned *event) +{ + unsigned char code[3];size_t used=99;esk_xtensa_fault fault;es_status st; + code[0]=(unsigned char)(word&255u);code[1]=(unsigned char)((word>>8)&255u);code[2]=(unsigned char)((word>>16)&255u); + st=esk_xtensa_window_execute(w,NULL,code,3,&used,event,&fault); + CHECK_EQ(used,st==ES_OK && !*event ? 3u : 0u);return st; +} +static void reset_fields_and_undefined_policy(void) +{ + unsigned selector,i; + for(selector=0;selector<2;selector++) { + esk_xtensa_windows w,before; + esk_xtensa_windows_init(&w,0x1234,NULL);w.base=5;w.start=0xabc;w.lcount=23;w.lbeg=0x12345678;w.lend=0x87654321; + w.sar=19;w.scompare1=0xaabbccdd;w.epc1=99;w.depc=88; + for(i=0;i<64;i++) w.ar[i]=0x100u+i; + for(i=0;i<16;i++) w.cpu.a[i]=0x200u+i; + w.irq.enabled=1;w.irq.intenable=0x12345678;w.irq.ccount=987;w.irq.compare[0]=123; + w.irq.latched=UINT32_MAX;w.irq.lines=UINT32_MAX;w.irq.waiting=1;w.irq.epc[2]=44;w.irq.excsave[2]=33; + before=w;CHECK_EQ(esk_xtensa_lx6_reset(&w,selector),ES_OK); + CHECK_EQ(w.cpu.pc,selector ? 0x40000400u : 0x50000000u);CHECK_EQ(w.ps,0x1f);CHECK_EQ(w.lcount,0); + CHECK_EQ(w.vecbase,0x40000000);CHECK_EQ(w.vecbase_enabled,1);CHECK_EQ(w.exceptions_enabled,1);CHECK_EQ(w.irq.enabled,1); + CHECK_EQ(w.base,before.base);CHECK_EQ(w.start,before.start);CHECK_EQ(w.lbeg,before.lbeg);CHECK_EQ(w.lend,before.lend); + CHECK_EQ(w.sar,before.sar);CHECK_EQ(w.scompare1,before.scompare1);CHECK_EQ(w.epc1,before.epc1);CHECK_EQ(w.depc,before.depc); + CHECK_EQ(w.irq.intenable,before.irq.intenable);CHECK_EQ(w.irq.ccount,before.irq.ccount);CHECK_EQ(w.irq.compare[0],123); + CHECK_EQ(w.irq.epc[2],44);CHECK_EQ(w.irq.excsave[2],33);CHECK_EQ(w.irq.waiting,0);CHECK_EQ(esk_xtensa_irq_pending(&w),0); + for(i=0;i<16;i++) { CHECK_EQ(w.cpu.a[i],before.cpu.a[i]);CHECK_EQ(w.ar[(20+i)&63u],before.cpu.a[i]); } + before=w;CHECK_EQ(esk_xtensa_lx6_reset(&w,2),ES_EINVAL);CHECK_EQ(memcmp(&w,&before,sizeof w),0); + } +} +static void register_access_and_rejection(void) +{ + unsigned reg,k,event;const uint32_t bases[]={0,0x40080000,0x50000000,0xfffffc00}; + for(reg=0;reg<16;reg++) for(k=0;k 0 + print(f'{cycles} compiled reset/worker cycles, {rom_entries} static test-ROM entries, 16 exact replays, {total} retired instructions; {held} reset-held turns') + + +if __name__ == '__main__': + main() diff --git a/tests/vendor/xtensa_integer/run_vectors.py b/tests/vendor/xtensa_integer/run_vectors.py new file mode 100644 index 000000000..dec46684d --- /dev/null +++ b/tests/vendor/xtensa_integer/run_vectors.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""Pinned ESP32 reset/vector facts plus separately assembled VECBASE accesses. + +Only aligned vector-base writes and the documented CPU subset are qualified. +No silicon low-bit/lock masking, complete reset, MMU or actual ROM boot claim. +""" +import argparse +import ctypes as C +from pathlib import Path +import random +import re +import subprocess +import tempfile +from run_execute import ROOT, Fault +from run_windows_c import Windows +from run_matrix import read_header + + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument('--core-isa', type=Path, required=True) + ap.add_argument('--assembler', required=True); ap.add_argument('--objcopy', required=True) + ap.add_argument('--cc', default='cc'); args = ap.parse_args() + source = read_header(args.core_isa, '60d20f76c7730b24041b9f55b16e0062f30bf85c7661c7ed215f050fedcdb344') + definitions = dict(re.findall(r'^#define\s+(XCHAL_\w+)\s+(\S+)', source, re.M)) + def fact(name): + value = definitions[name] + return fact(value) if value.startswith('XCHAL_') else int(value, 0) + assert fact('XCHAL_HAVE_VECBASE') == 1 and fact('XCHAL_NUM_AREGS') == 64 + window = [fact('XCHAL_WINDOW_' + name + '_VECOFS') for name in ['OF4', 'UF4', 'OF8', 'UF8', 'OF12', 'UF12']] + general = [fact('XCHAL_' + name + '_VECOFS') for name in ['KERNEL', 'USER', 'DOUBLEEXC']] + high = [fact(f'XCHAL_INTLEVEL{level}_VECOFS') for level in range(2, 8)] + reset_pc = [fact(f'XCHAL_RESET_VECTOR{n}_VADDR') for n in range(2)] + reset_base = fact('XCHAL_VECBASE_RESET_VADDR') + levels = {int(n): int(level) for n, level in re.findall(r'#define\s+XCHAL_INT(\d+)_LEVEL\s+(\d+)', source)} + kinds = dict((int(n), kind) for n, kind in re.findall(r'#define\s+XCHAL_INT(\d+)_TYPE\s+XTHAL_INTTYPE_(\w+)', source)) + inputs = {level: next(n for n in levels if levels[n] == level and kinds[n] in ['EXTERN_LEVEL', 'EXTERN_EDGE', 'NMI']) + for level in [1, 2, 3, 4, 5, 7]} + specs = [(f'{op} a{reg},VECBASE', op, reg) for op in ['rsr', 'wsr', 'xsr'] for reg in range(16)] + assembly = [asm for asm, _, _ in specs] + ['movi a4,0', 'retw'] + rng = random.Random(0xe7); accesses = 0; dispatches = 0; resets = 0 + with tempfile.TemporaryDirectory(prefix='embsim-vectors-') as tmp: + out = Path(tmp); src, obj, binary = [out / ('vectors.' + suffix) for suffix in ['S', 'o', 'bin']] + src.write_text('.text\n.begin no-transform\n' + '\n'.join(assembly) + '\n.end no-transform\n', encoding='utf-8') + subprocess.run([args.assembler, str(src), '-o', str(obj)], check=True) + subprocess.run([args.objcopy, '-O', 'binary', '--only-section=.text', str(obj), str(binary)], check=True) + code = binary.read_bytes(); assert len(code) == len(assembly) * 3 + lib = out / 'vectors.so' + subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', + '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), + *[str(ROOT / 'core/mcu/xtensa' / n) for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']], + '-o', str(lib)], check=True) + api = C.CDLL(str(lib)); wp = C.POINTER(Windows) + def bind(name, result, *params): + fn = getattr(api, 'esk_xtensa_' + name); fn.restype = result; fn.argtypes = list(params); return fn + init = bind('windows_init', None, wp, C.c_uint32, C.c_void_p) + reset = bind('lx6_reset', C.c_int, wp, C.c_uint) + configure = bind('vecbase_configure', C.c_int, wp, C.c_uint32) + execute = bind('window_execute', C.c_int, wp, C.c_void_p, C.c_void_p, C.c_size_t, C.POINTER(C.c_size_t), C.POINTER(C.c_uint), C.POINTER(Fault)) + exception = bind('raise_exception', C.c_int, wp, C.c_uint, C.c_uint32, C.c_int, C.POINTER(C.c_uint)) + line = bind('irq_line', C.c_int, wp, C.c_uint, C.c_int) + poll = bind('irq_poll', C.c_int, wp, C.POINTER(C.c_uint)) + def state(base): + w = Windows(); init(C.byref(w), 0x40080000, None) + assert reset(C.byref(w), 1) == 0 and configure(C.byref(w), base) == 0 + return w + def run(w, index): + instruction = C.create_string_buffer(code[index * 3:index * 3 + 3]) + used = C.c_size_t(); event = C.c_uint(); fault = Fault() + assert execute(C.byref(w), None, instruction, 3, C.byref(used), C.byref(event), C.byref(fault)) == 0 + return used.value, event.value + for index, (_, op, reg) in enumerate(specs): + for _ in range(64): + old, new = [rng.getrandbits(32) & ~1023 for _ in range(2)] + w = state(old) + for n in range(16): w.cpu.a[n] = rng.getrandbits(32) + w.cpu.a[reg] = new; expected = list(w.cpu.a) + base = new if op != 'rsr' else old + if op != 'wsr': expected[reg] = old + pc = w.cpu.pc; assert run(w, index) == (3, 0) + assert w.cpu.pc == pc + 3 and w.vecbase == base and list(w.cpu.a) == expected + assert list(w.vectors) == [base + offset for offset in window] + assert list(w.exception_vectors) == [base + offset for offset in general] + assert list(w.irq.vectors)[2:] == [base + offset for offset in high] + accesses += 1 + for _ in range(64): + base = rng.getrandbits(32) & ~1023 + for selector in range(2): + w = state(base); w.lcount = 77; w.ps = 0x40000 + assert reset(C.byref(w), selector) == 0 + assert w.cpu.pc == reset_pc[selector] and w.vecbase == reset_base and w.lcount == 0 + assert w.ps == (0x1f if fact('XCHAL_HAVE_INTERRUPTS') else 0x10) + resets += 1 + for index, ps in enumerate([0, 32, 16]): + w = state(base); w.ps = ps; event = C.c_uint() + assert exception(C.byref(w), 1, 0, 0, C.byref(event)) == 0 + assert event.value == 7 + index and w.cpu.pc == base + general[index]; dispatches += 1 + for n in range(1, 4): + w = state(base); w.ps = 1 << 18; w.start = 3 | (4 if n == 1 else 8 if n == 2 else 0) + assert run(w, len(specs)) == (0, n * 2 - 1) and w.cpu.pc == base + window[(n - 1) * 2] + w = state(base); w.ps = 1 << 18; w.base = 3; w.start = 8; w.cpu.a[0] = (n << 30) | 0x5678 + assert run(w, len(specs) + 1) == (0, n * 2) and w.cpu.pc == base + window[n * 2 - 1] + dispatches += 2 + for level, input_number in inputs.items(): + w = state(base); w.ps = 0; w.irq.intenable = 1 << input_number; event = C.c_uint() + assert line(C.byref(w), input_number, 1) == 0 and poll(C.byref(w), C.byref(event)) == 0 + assert event.value == 10 + level and w.cpu.pc == base + (general[0] if level == 1 else high[level - 2]) + dispatches += 1 + print(f'{accesses} assembled VECBASE accesses, {dispatches} vendor-offset dispatches and {resets} reset-vector comparisons pass') + + +if __name__ == '__main__': + main() diff --git a/tests/vendor/xtensa_integer/run_windows_c.py b/tests/vendor/xtensa_integer/run_windows_c.py index 8b99be824..547d013eb 100644 --- a/tests/vendor/xtensa_integer/run_windows_c.py +++ b/tests/vendor/xtensa_integer/run_windows_c.py @@ -22,7 +22,7 @@ class Windows(C.Structure): _fields_=[('cpu',State),('ar',C.c_uint32*64),('ps',C.c_uint32),('base',C.c_uint32), ('start',C.c_uint32),('epc1',C.c_uint32),('lbeg',C.c_uint32),('lend',C.c_uint32), ('lcount',C.c_uint32),('sar',C.c_uint32),('exccause',C.c_uint32),('excvaddr',C.c_uint32), - ('depc',C.c_uint32),('scompare1',C.c_uint32),('exceptions_enabled',C.c_uint32),('exception_vectors',C.c_uint32*3),('irq',Interrupts),('vectors',C.c_uint32*6)] + ('depc',C.c_uint32),('scompare1',C.c_uint32),('exceptions_enabled',C.c_uint32),('exception_vectors',C.c_uint32*3),('irq',Interrupts),('vectors',C.c_uint32*6),('vecbase',C.c_uint32),('vecbase_enabled',C.c_uint32)] NESTED=''' From d3f6c70d3c61bc8a3e127ee79b7da65f917bf979 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 03:56:44 +0000 Subject: [PATCH 19/57] Execute ESP32 code through per-core read-only flash MMU mappings --- CHANGELOG.md | 14 ++ core/mcu/esp32/fabric.c | 43 +++++- core/mcu/esp32/fabric.h | 13 +- core/mcu/esp32/flashmap.c | 108 ++++++++++++++ core/mcu/esp32/flashmap.h | 24 +++ docs/STATUS.md | 12 +- docs/engineering/esp32-dual-core.md | 20 ++- docs/engineering/esp32-flash-mapping.md | 147 +++++++++++++++++++ tests/unit/test_esp32_flashmap.c | 137 +++++++++++++++++ tests/vendor/README.md | 15 ++ tests/vendor/xtensa_integer/run_controls.py | 2 +- tests/vendor/xtensa_integer/run_flash_c.py | 146 ++++++++++++++++++ tests/vendor/xtensa_integer/run_flashmap.py | 109 ++++++++++++++ tests/vendor/xtensa_integer/run_ipi_c.py | 2 +- tests/vendor/xtensa_integer/run_machine_c.py | 2 +- tests/vendor/xtensa_integer/run_reset_c.py | 30 +++- 16 files changed, 805 insertions(+), 19 deletions(-) create mode 100644 core/mcu/esp32/flashmap.c create mode 100644 core/mcu/esp32/flashmap.h create mode 100644 docs/engineering/esp32-flash-mapping.md create mode 100644 tests/unit/test_esp32_flashmap.c create mode 100644 tests/vendor/xtensa_integer/run_flash_c.py create mode 100644 tests/vendor/xtensa_integer/run_flashmap.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 35e9895d0..2fb1921fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental ESP32 read-only mapped flash + +- Added copied whole-page flash backing, per-core PID0/1 normal-mode MMU tables, + real cache-control/mask addresses and a functional flush/enable sequence. + On-chip memory keeps priority; invalid/masked/out-of-extent reads fail without + fabricated bytes. Allocation/replacement failures preserve old state. +- Table edits require the destination cache disabled and a fresh flush. No live + remap coherence, cache tags/timing, SPI programming, high-PID/PSRAM/lock/page + modes, actual ROM or complete SDK/board boot support is claimed. +- Added 7,289 native assertions, 488 parsed-vendor translations and 256 compiled + per-core mapped workers (64 exact replays, 11,264 retired instructions). The + original test-ROM reset path also passes 128 cycles into a mapped C worker + (16 replays, 15,360 retired instructions). + ### Experimental ESP32 CPU reset and vector relocation - Added the supported CPU reset subset with explicit static vector selection, diff --git a/core/mcu/esp32/fabric.c b/core/mcu/esp32/fabric.c index 1eeaa58a2..3f357b2a7 100644 --- a/core/mcu/esp32/fabric.c +++ b/core/mcu/esp32/fabric.c @@ -2,12 +2,13 @@ /* Copyright 2026 Junior Deogracias */ /* Address facts: ESP32 TRM v5.9, section 3.3.2. No vendor code copied. */ #include "mcu/esp32/fabric.h" +#include "mcu/esp32/flashmap.h" #include struct view { struct esk_esp32_fabric *f;unsigned cpu; }; struct esk_esp32_fabric { esk_alloc *a; unsigned char sram0[0x20000],sram1[0x20000],sram2[0x32000],fast[0x2000],slow[0x2000]; - unsigned char *rom[2]; + unsigned char *rom[2];esk_esp32_flashmap *flash; unsigned remap[2];struct view view[2]; }; struct location { unsigned char *byte;int readonly,word_only,executable,atomic; }; @@ -54,13 +55,34 @@ static es_status access(struct view *v,uint32_t address,unsigned bytes,int fetch else { for(i=0;if->flash) { + st=esk_esp32_flashmap_reg_read(view->f->flash,a,n,v);if(st!=ES_EUNSUPPORTED) return st; + st=esk_esp32_flashmap_read(view->f->flash,view->cpu,a,n,0,v);if(st!=ES_EUNSUPPORTED) return st; + } + return access(view,a,n,0,0,v); +} +static es_status fetch_bus(void *ctx,uint32_t a,unsigned n,uint32_t *v) +{ + struct view *view=(struct view *)ctx;es_status st; + if(!view) return ES_EINVAL; + if(view->f->flash) { st=esk_esp32_flashmap_read(view->f->flash,view->cpu,a,n,1,v);if(st!=ES_EUNSUPPORTED) return st; } + return access(view,a,n,1,0,v); +} +static es_status write_bus(void *ctx,uint32_t a,unsigned n,uint32_t value) +{ + struct view *view=(struct view *)ctx;es_status st; + if(!view) return ES_EINVAL; + if(view->f->flash) { st=esk_esp32_flashmap_reg_write(view->f->flash,a,n,value);if(st!=ES_EUNSUPPORTED) return st; } + return access(view,a,n,0,1,&value); +} static es_status cas_bus(void *ctx,uint32_t address,uint32_t expected,uint32_t desired,uint32_t *observed) { struct view *v=(struct view *)ctx;struct location at;uint32_t old;es_status st; - if(!observed || (address&3u)) return ES_EINVAL; + if(!v || !observed || (address&3u)) return ES_EINVAL; st=locate(v,address,&at);if(st!=ES_OK) return st; if(!at.atomic) return ES_EUNSUPPORTED; st=read_bus(ctx,address,4,&old);if(st!=ES_OK) return st; @@ -82,6 +104,7 @@ void esk_esp32_fabric_destroy(esk_esp32_fabric *f) if(!f) return; if(f->rom[0]) esk_free(f->a,f->rom[0],0x60000); if(f->rom[1]) esk_free(f->a,f->rom[1],0x10000); + esk_esp32_flashmap_destroy(f->flash); esk_free(f->a,f,sizeof *f); } es_status esk_esp32_fabric_rom(esk_esp32_fabric *f,unsigned which,const unsigned char *data,size_t len) @@ -91,6 +114,16 @@ es_status esk_esp32_fabric_rom(esk_esp32_fabric *f,unsigned which,const unsigned copy=(unsigned char *)esk_malloc(f->a,len);if(!copy) return ES_ENOMEM; memcpy(copy,data,len);if(f->rom[which]) esk_free(f->a,f->rom[which],len);f->rom[which]=copy;return ES_OK; } +es_status esk_esp32_fabric_flash(esk_esp32_fabric *f,const unsigned char *data,size_t len) +{ + esk_esp32_flashmap *created=NULL;es_status st; + if(!f || !data || !len || (len&65535u) || len>0x1000000u) return ES_EINVAL; + if(f->flash) return esk_esp32_flashmap_install(f->flash,data,len); + st=esk_esp32_flashmap_create(f->a,&created);if(st!=ES_OK) return st; + st=esk_esp32_flashmap_install(created,data,len); + if(st!=ES_OK) { esk_esp32_flashmap_destroy(created);return st; } + f->flash=created;return ES_OK; +} es_status esk_esp32_fabric_remap(esk_esp32_fabric *f,unsigned cpu,int enabled) { if(!f || cpu>1) return ES_EINVAL; diff --git a/core/mcu/esp32/fabric.h b/core/mcu/esp32/fabric.h index 9f720a330..93888c3fb 100644 --- a/core/mcu/esp32/fabric.h +++ b/core/mcu/esp32/fabric.h @@ -7,13 +7,22 @@ typedef struct esk_esp32_fabric esk_esp32_fabric; /* Experimental static internal-memory profile, not the full SoC bus. SRAM0 * non-cache 128K, SRAM1 128K (reversed instruction words), SRAM2 200K, RTC - * fast/slow 8K each. RTC fast is PRO-only. No cache/MMU/MPU, flash, peripherals, - * DMA, RTC power/retention or physical reset qualification. RAM starts zero only + * fast/slow 8K each. RTC fast is PRO-only. Optional read-only flash mapping + * is described below. No full cache/MMU/MPU, peripheral/DMA, RTC power/retention + * or physical reset qualification. RAM starts zero only * for deterministic setup. Serialized single-thread calls only; no re-entry. */ es_status esk_esp32_fabric_create(esk_alloc *a,esk_esp32_fabric **out); void esk_esp32_fabric_destroy(esk_esp32_fabric *f); /* Optional externally provided ROM0/ROM1, exact 384K/64K. No ROM is fabricated. */ es_status esk_esp32_fabric_rom(esk_esp32_fabric *f,unsigned which,const unsigned char *data,size_t len); +/* Opt into copied read-only external flash: whole 64K pages, at most 16MB. + * Adds PID0/1 normal-mode MMU tables and a bounded cache-control/flush profile. + * Tables start invalid by fixture policy. Edit while target cache disabled, + * flush via clear/set FLUSH_ENA, then enable. No cache tags/timing, SPI writes, + * PSRAM/high-PID/lock/special modes or physical flash-part identity claim. + * Replacement requires both caches disabled and preserves mappings; allocation + * failure leaves the old image/state intact. Host calls only between turns. */ +es_status esk_esp32_fabric_flash(esk_esp32_fabric *f,const unsigned char *data,size_t len); /* Host-controlled boot-remap bit, not a DPORT register model. */ es_status esk_esp32_fabric_remap(esk_esp32_fabric *f,unsigned cpu,int enabled); /* All-or-nothing RAM image write through the selected core's address view. */ diff --git a/core/mcu/esp32/flashmap.c b/core/mcu/esp32/flashmap.c new file mode 100644 index 000000000..50ca8fc95 --- /dev/null +++ b/core/mcu/esp32/flashmap.c @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* Facts: ESP32 TRM v5.9 ch.3/4/12, ESP-IDF v4.4.7 soc/mmu.h and dport_reg.h. + * Original functional translation implementation, no vendor controller code. */ +#include "mcu/esp32/flashmap.h" +#include +struct esk_esp32_flashmap { + esk_alloc *a;unsigned char *image;size_t size; + uint32_t table[2][256],ctrl[2],ctrl1[2],dirty[2]; +}; +es_status esk_esp32_flashmap_create(esk_alloc *a,esk_esp32_flashmap **out) +{ + esk_esp32_flashmap *f;unsigned cpu,i; + if(!out) return ES_EINVAL; + *out=NULL;if(!a) return ES_EINVAL; + f=(esk_esp32_flashmap *)esk_calloc(a,1,sizeof *f);if(!f) return ES_ENOMEM; + f->a=a; + for(cpu=0;cpu<2;cpu++) { + f->ctrl[cpu]=0x10;f->ctrl1[cpu]=0x8ff;f->dirty[cpu]=1; + for(i=0;i<256;i++) f->table[cpu][i]=0x100; + } + *out=f;return ES_OK; +} +void esk_esp32_flashmap_destroy(esk_esp32_flashmap *f) +{ + if(!f) return; + if(f->image) esk_free(f->a,f->image,f->size); + esk_free(f->a,f,sizeof *f); +} +es_status esk_esp32_flashmap_install(esk_esp32_flashmap *f,const unsigned char *data,size_t len) +{ + unsigned char *copy; + if(!f || !data || !len || (len&65535u) || len>0x1000000u) return ES_EINVAL; + if((f->ctrl[0]|f->ctrl[1])&8u) return ES_EUNSUPPORTED; + copy=(unsigned char *)esk_malloc(f->a,len);if(!copy) return ES_ENOMEM; + memcpy(copy,data,len);if(f->image) esk_free(f->a,f->image,f->size); + f->image=copy;f->size=len;f->dirty[0]=f->dirty[1]=1;return ES_OK; +} +/* kind=0 MMU entry, 1 control, 2 masks. Only PID0/1 entry ranges exist here. */ +static es_status decode(uint32_t address,unsigned bytes,unsigned *cpu,unsigned *kind,unsigned *index) +{ + if((address>=0x3ff10000 && address<0x3ff10400)||(address>=0x3ff12000 && address<0x3ff12400)) { + *cpu=address>=0x3ff12000 ? 1u : 0u;*kind=0;*index=(address-(0x3ff10000u+*cpu*0x2000u))/4; + } else if((address>=0x3ff00040 && address<0x3ff00048)||(address>=0x3ff00058 && address<0x3ff00060)) { + *cpu=address>=0x3ff00058 ? 1u : 0u;*kind=address-(0x3ff00040u+*cpu*24u)<4 ? 1u : 2u;*index=0; + } else return ES_EUNSUPPORTED; + if(bytes!=4 || (address&3u)) return ES_EINVAL; + return ES_OK; +} +es_status esk_esp32_flashmap_reg_read(const esk_esp32_flashmap *f,uint32_t address,unsigned bytes,uint32_t *value) +{ + unsigned cpu,kind,index;es_status st; + if(!f || !value) return ES_EINVAL; + st=decode(address,bytes,&cpu,&kind,&index);if(st!=ES_OK) return st; + *value=kind==0 ? f->table[cpu][index] : kind==1 ? f->ctrl[cpu] : f->ctrl1[cpu];return ES_OK; +} +es_status esk_esp32_flashmap_reg_write(esk_esp32_flashmap *f,uint32_t address,unsigned bytes,uint32_t value) +{ + unsigned cpu,kind,index;uint32_t next;es_status st; + if(!f) return ES_EINVAL; + st=decode(address,bytes,&cpu,&kind,&index);if(st!=ES_OK) return st; + if(kind==0) { + if((value&~UINT32_C(0x1ff)) || (f->ctrl[cpu]&8u)) return ES_EUNSUPPORTED; + f->table[cpu][index]=value;f->dirty[cpu]=1; + } else if(kind==1) { + int flush=!(f->ctrl[cpu]&16u) && (value&16u); + /* Reject unsupported defined RW fields. RO/reserved bits are ignored. */ + if(value&UINT32_C(0x10fc4)) return ES_EUNSUPPORTED; + if(flush && ((f->ctrl[cpu]|value)&8u)) return ES_EUNSUPPORTED; + if((value&8u) && f->dirty[cpu]) return ES_EUNSUPPORTED; + next=(value&24u)|(f->ctrl[cpu]&32u); + if(!(value&16u)) next&=~32u; + if(flush) { next|=32u;f->dirty[cpu]=0; } + f->ctrl[cpu]=next; + } else { + /* Preserve unqualified PSRAM/page/power/error-control reset fields. */ + if((value&0x3fe8u)!=(0x8ffu&0x3fe8u)) return ES_EUNSUPPORTED; + f->ctrl1[cpu]=0x8e8u|(value&0x17u); + } + return ES_OK; +} +static es_status resolve(const esk_esp32_flashmap *f,unsigned cpu,uint32_t address,size_t *physical) +{ + uint32_t page,mask,entry; + if(address>=0x3f400000 && address<0x3f800000) { page=(address-0x3f400000)>>16;mask=16; } + else if(address>=0x400c2000 && address<0x40c00000) { + unsigned region=(address-0x40000000)>>22; + page=64u+((address-0x40000000)>>16);mask=1u<ctrl[cpu]&8u) || (f->ctrl1[cpu]&mask)) return ES_EUNSUPPORTED; + entry=f->table[cpu][page];if(entry&0x100u) return ES_EUNSUPPORTED; + *physical=(size_t)(entry&255u)*65536u+(address&65535u); + if(*physical>=f->size) return ES_EUNSUPPORTED; + return ES_OK; +} +es_status esk_esp32_flashmap_read(const esk_esp32_flashmap *f,unsigned cpu,uint32_t address,unsigned bytes,int fetch,uint32_t *value) +{ + size_t physical[4];unsigned i;uint32_t result=0;es_status st;int instruction; + if(!f || cpu>1 || !value) return ES_EINVAL; + instruction=address>=0x400c2000 && address<0x40c00000; + if(!instruction && !(address>=0x3f400000 && address<0x3f800000)) return ES_EUNSUPPORTED; + if(!f->image || (fetch && !instruction)) return ES_EUNSUPPORTED; + if(bytes<1 || bytes>4 || (!fetch && (bytes==3 || (address&(bytes-1u))))) return ES_EINVAL; + if(!fetch && instruction && bytes!=4) return ES_EUNSUPPORTED; + for(i=0;iimage[physical[i]]<<(8*i); + *value=result;return ES_OK; +} diff --git a/core/mcu/esp32/flashmap.h b/core/mcu/esp32/flashmap.h new file mode 100644 index 000000000..02f62dbcc --- /dev/null +++ b/core/mcu/esp32/flashmap.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_ESP32_FLASHMAP_H +#define ESK_ESP32_FLASHMAP_H +#include "util/alloc.h" +/* Read-only original-ESP32 flash/MMU functional profile, PID0/1 normal mode. + * Per-core 256-entry tables, 64K pages, access masks and explicit flush/enable + * sequence. No cache tags/replacement/latency, physical SPI/JEDEC programming, + * PSRAM, high PIDs, lock/special/page modes, pool reuse or fault IRQ modeling. + * To keep immutable-image reads qualified without cache-coherence guesses, + * table edits require the destination cache disabled and a fresh flush before + * enabling. Flash replacement requires both caches disabled. Unsupported modes + * fail unchanged. Initial invalid entries are fixture policy, not silicon reset. + * No concurrent callers or re-entry. */ +typedef struct esk_esp32_flashmap esk_esp32_flashmap; +es_status esk_esp32_flashmap_create(esk_alloc *a,esk_esp32_flashmap **out); +void esk_esp32_flashmap_destroy(esk_esp32_flashmap *f); +/* Complete provided backing extent: 1..256 whole 64K pages, copied privately. + * No chip identity/capacity is inferred and no missing bytes are fabricated. */ +es_status esk_esp32_flashmap_install(esk_esp32_flashmap *f,const unsigned char *data,size_t len); +es_status esk_esp32_flashmap_reg_read(const esk_esp32_flashmap *f,uint32_t address,unsigned bytes,uint32_t *value); +es_status esk_esp32_flashmap_reg_write(esk_esp32_flashmap *f,uint32_t address,unsigned bytes,uint32_t value); +es_status esk_esp32_flashmap_read(const esk_esp32_flashmap *f,unsigned cpu,uint32_t address,unsigned bytes,int fetch,uint32_t *value); +#endif diff --git a/docs/STATUS.md b/docs/STATUS.md index a48876a91..c5d043276 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -55,7 +55,7 @@ two-context harness shares this RAM while keeping CPU/interrupt/clock state private. Native fixtures cover address/permission/remap/fault/wait boundaries; 512 actual ESP32 compiled task executions and 128 replay comparisons pass through the native bus (229,322 instructions). This remains a bounded memory/execution -harness: cache/MMU, complete reset domains, full SMP and SoC boot are absent. +harness: complete cache/MMU, reset domains and full SMP and SoC boot are absent. L32AI/S32RI now provide acquire/release ordering under the synchronous completed callback contract (30,732 native assertions). The compiled shared-bus acceptance includes acquire/CAS/release spinlocks protecting ordinary writes. Asynchronous @@ -69,7 +69,7 @@ and 103 integration assertions; pinned-header referees pass 4,288 routes and 512 combined-source/mask cases. Actual ESP32 C sender/C handler acceptance with an assembled entry/return wrapper passes 256 mailbox executions, 4,224 interrupts and 128 exact replays (512,128 retired instructions), in both directions. Peripheral generators, PID hardware, complete reset domains, APB timing, -flash/cache and boot remain absent; no complete ISR/RTOS or timing claim follows. +complete flash/cache and boot remain absent; no complete ISR/RTOS or timing claim follows. APP clock/stall fields and the ROM-consumed boot-address register now work through the machine bus. APP starts clock-disabled; fixture setup explicitly enables it. Gate/stall stops fetch/retirement and resumes at the same PC, without @@ -92,6 +92,14 @@ state. Evidence: 2,281 CPU/104 machine assertions, 3,072 assembled VECBASE acces 960 vendor-offset dispatches and 128 reset-address comparisons. Original test-ROM handoff passes 128 compiled supervisor/worker cycles and 16 exact replays. Actual Espressif ROM, full reset-domain behavior and SDK/board boot remain absent. +An opt-in [read-only mapped-flash profile](engineering/esp32-flash-mapping.md) +now supports PID0/1 normal-mode per-core tables, 64 KiB pages, access masks and +explicit flush/enable sequencing. Evidence: 7,289 native assertions, 488 parsed +vendor-fact translations, 256 compiled mapped workers and 64 exact replays. +Compiled PRO setup plus original test-ROM handoff also passes 128 reset cycles +into a mapped worker. Immutable backing and disabled-cache table edits bound this +profile; SPI programming, real cache tags/timing, live SDK mmap/invalidation, +PSRAM/high PIDs and actual ROM/SDK/board boot remain unimplemented. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 2d2795c25..964556232 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -1,6 +1,6 @@ # Original ESP32 dual-core target: identity and implementation gates -**Status: firmware inspection, experimental Xtensa execution, shared internal RAM, interrupt routing, APP controls and bounded reset/vector profiles implemented; no runnable board target is added.** Requested by +**Status: firmware inspection, experimental Xtensa execution, shared internal RAM, interrupt routing, APP controls and bounded reset/vector and read-only mapped-flash profiles implemented; no runnable board target is added.** Requested by the owner on 2026-11-04. This is the original ESP32 family, not ESP32-C3, ESP32-S2 or ESP32-S3. The generic marketplace title “ESP-32 / ESP-32S WiFi+Bluetooth Dual Cores” does not identify a reproducible board revision. @@ -524,7 +524,8 @@ A host-configured per-core boot-remap view aliases the first 32 KiB of ROM0 to SRAM1 instruction addresses `400B0000–400B8000`, disabling that original instruction alias for the selected core while preserving its data view and the other core's mapping. This is not a DPORT register implementation. Cache SRAM, -flash/MMU windows, peripheral addresses and unmapped holes fail explicitly. +unknown peripheral addresses and unmapped holes fail explicitly. Flash/MMU +windows require the separate opt-in mapped-flash profile below. ROM must be provided externally at its exact region size; none is fabricated or bundled. Host RAM loading preflights the entire range before writing. A failed ROM allocation preserves the installed ROM. @@ -744,6 +745,21 @@ worker pass 128 reset cycles through an original temporary test-ROM program (11,904 retired instructions, 16 exact replays). This is not Espressif ROM, complete silicon reset, MMU/cache or real SDK/board boot qualification. +## Implemented: read-only mapped flash for PID0/1 + +The [flash mapping contract and reproduction](esp32-flash-mapping.md) describe +the two normal-mode tables, actual cache-control/mask addresses, immutable backing, +flush sequencing and access checks. Compiled firmware can program mappings in +internal RAM and execute code/read data at flash virtual addresses. Unsupported +live-remap/cache-tag/timing, SPI programming, PSRAM/high-PID and lock/page modes +remain explicit gaps; this does not qualify full SDK cache/mmap behavior. + +Evidence: 7,289 native assertions, 488 vendor-fact translations, six control/flush +comparisons and 256 actual compiled mapped workers with per-core code/data +mappings (11,264 retired instructions, 64 replays). The original test-ROM reset +path also passes 128 cycles into a flash-mapped worker (15,360 instructions, +16 replays). Actual Espressif ROM/SDK/board boot remains unimplemented. + ## Implemented: standalone firmware integrity inspection `embsim image-info esp32 app.bin` inspects a plaintext original-ESP32 image diff --git a/docs/engineering/esp32-flash-mapping.md b/docs/engineering/esp32-flash-mapping.md new file mode 100644 index 000000000..243e5e45d --- /dev/null +++ b/docs/engineering/esp32-flash-mapping.md @@ -0,0 +1,147 @@ +# Original ESP32: read-only mapped-flash profile + +This opt-in profile adds firmware-visible flash mappings to the +[experimental ESP32 foundation](esp32-dual-core.md). Compiled code can configure +MMU entries and execute/read through them on both cores. It does not register a +board target, emulate a physical flash chip or establish real ROM/SDK boot. + +## References and scope + +Addressing and controls come from ESP32 TRM v5.9 sections 3.3.3–3.3.4, +4.3.2.2 and chapter 12, plus pinned ESP-IDF v4.4.7 headers: + +- [soc/mmu.h](https://github.com/espressif/esp-idf/blob/v4.4.7/components/soc/esp32/include/soc/mmu.h), + SHA-256 `59842853da8931e4a5fd61ec0cad565a38b691d53a96110439d9d0aadaf920b9`. +- [soc/dport_reg.h](https://github.com/espressif/esp-idf/blob/v4.4.7/components/soc/esp32/include/soc/dport_reg.h) + and [soc/soc.h](https://github.com/espressif/esp-idf/blob/v4.4.7/components/soc/esp32/include/soc/soc.h), + with hashes recorded in the main foundation document. +- The pinned ESP32 `core-isa.h` supplies instruction/data bus window names and + addresses; its hash is also recorded there. + +`esk_esp32_fabric_flash` copies a provided backing image of 1..256 whole 64 KiB +pages, at most 16 MiB. The supplied extent is authoritative; missing bytes, +capacity, a JEDEC identity or a board's flash part are not inferred. The private +image is immutable during mapped execution. Replacement requires both caches +disabled, preserves mappings and requires a fresh flush before re-enable. +Allocation failure preserves the old image and all controller state. No SPI +read/program/erase protocol, write protection, quad mode, encryption, secure boot +or physical flash loading is implemented by this interface. + +The profile qualifies normal-mode PID0/1 mappings. A PID controller, PID2..7, +single-IRAM mode, PSRAM, alternate page modes, locks, cache pool mux/reuse, +cache tags/replacement and cache/SPI cycle timing remain unsupported. Changes to +unimplemented defined control fields fail without changing the register. + +## Virtual and physical mappings + +Each core has 256 implemented entries with 64 KiB pages. Bits 7..0 select one +of 256 physical pages. Bit 8 set means invalid. Other entry bits must remain +zero in this profile. Entries start at the invalid encoding `100` as a fixture +policy, not a claim about silicon MMU-table reset contents. + +| Region | Virtual addresses | Local entries | Cache mask | +| --- | --- | --- | --- | +| DROM0 data | `3F400000–3F7FFFFF` | 0..63 | CTRL1 bit 4 | +| IRAM0 instruction window | `40000000–403FFFFF` | 64..127 | CTRL1 bit 0 | +| IRAM1 instruction window | `40400000–407FFFFF` | 128..191 | CTRL1 bit 1 | +| IROM0 instruction window | `40800000–40BFFFFF` | 192..255 | CTRL1 bit 2 | + +On-chip memory retains priority in the first instruction window. Only +`400C2000` and above can reach flash: ROM, internal SRAM, cache-pool holes and +RTC fast memory below that address cannot be replaced by a flash table entry. +APP cannot gain access to PRO-only RTC fast memory through this mapping. + +DROM0 supports data bytes, halfwords and words; instruction fetch is forbidden. +Instruction windows support fetch bytes, including cross-page instructions, and +aligned data words for literal loads. Narrow data accesses there are unsupported. +Data accesses require natural alignment. Reads preflight all requested bytes; +invalid/masked entries or physical addresses outside the supplied extent fail +without returning fabricated values or wrapping to another page. Writes and CAS +through mapped flash are unsupported. These failures remain ordinary emulator +bus diagnostics; hardware stall/MMU-error interrupt behavior is not modeled. + +| Interface | PRO addresses | APP addresses | +| --- | --- | --- | +| Implemented MMU table | `3FF10000–3FF103FC` | `3FF12000–3FF123FC` | +| CACHE_CTRL | `3FF00040` | `3FF00058` | +| CACHE_CTRL1 | `3FF00044` | `3FF0005C` | + +Either CPU's privileged PID0/1 bus view can program either table. Other table +ranges remain unsupported. MMIO requires aligned 32-bit data access; MMIO fetch +and atomic stores are not supported. + +## Access gates and flush sequence + +CACHE_CTRL starts with documented field value `10`: cache disabled, FLUSH_ENA +set and FLUSH_DONE clear. CACHE_CTRL1 starts at `8FF`, with the supported bus +masks set. Read-only/reserved control bits ignore writes. Only cache enable, +flush and the four flash bus masks are implemented; unqualified defined RW +fields must retain their documented default values. + +A supported mapping sequence is: + +1. Execute configuration code from internal RAM and disable the destination cache. +2. Write the destination table and clear the required CTRL1 bus masks. +3. Clear FLUSH_ENA, then set it while the cache remains disabled. +4. Observe FLUSH_DONE and enable the cache. + +Flush completion is synchronous in this functional profile. No busy duration, +power-on flush progression, hit/miss latency or instruction-to-clock conversion +is claimed. A self-disabling access commits before a subsequent mapped fetch +fails. Internal RAM/ROM accesses remain available independently of flash masks. + +**Live cache remapping is not qualified.** Hardware/SDK software can update +mappings with appropriate invalidation, but this profile rejects table writes +while the destination cache is enabled and requires a fresh flush after changes. +It also rejects starting flush with an enabled cache. Immutable backing plus +this restricted transition sequence avoids inventing live-remap coherence or +cache contents. Dynamic `spi_flash_mmap`/cache-invalidate compatibility needs +further implementation; successful reads here do not establish that support. + +CPU-only reset leaves this provider state outside the CPU within the current +model. Broader reset/retention domains remain unqualified. Actual Espressif ROM, +SPI boot/loading, cache SRAM pool access and complete SDK startup are still +missing. The [bounded reset profile](esp32-reset-vectors.md) continues to fetch +the static reset entry; attaching flash never creates a ROM or boot-address PC +shortcut. + +## Acceptance and reproduction + +- `test_esp32_flashmap`: 7,289 assertions across both complete normal-mode tables, + all physical pages in a 16 MiB backing, masks, cross-page fetches, on-chip + precedence, invalid/unsupported operations, extent bounds, private-copy + semantics and transactional allocation/replacement failures. +- `run_flashmap.py`: 488 translations using parsed vendor window/table facts, + plus six parsed initial-control/flush comparisons and per-window mask checks. +- `run_flash_c.py`: 256 actual ESP32-compiled mapped workers, 64 exact PC/result + replays and 11,264 retired instructions, including 2,048 instructions fetched + from flash. Compiled internal-RAM code programs each core's table/controls; + the same virtual code/data addresses then resolve to different physical pages + and return the expected per-core results. No Python memory callbacks. +- `run_reset_c.py --flash-worker`: compiled PRO code configures APP's MMU/cache + while reset is held, then releases it into the original test-ROM program. + Firmware reads BOOT_ADDR and calls the mapped C worker. It passes 128 reset + cycles, 128 static ROM entries, 16 exact PC/event replays and 15,360 retired + instructions (1,664 from mapped flash). Its 5,904 held-reset turns are + instruction attempts, not physical cycles. Shared-state, source and stack + checks remain in effect. The test ROM is original fixture code, not Espressif + ROM or evidence of real cold boot. + +```sh +python tests/vendor/xtensa_integer/run_flashmap.py \ + --dport-reg /path/to/esp-idf/components/soc/esp32/include/soc/dport_reg.h \ + --soc-header /path/to/esp-idf/components/soc/esp32/include/soc/soc.h \ + --mmu-header /path/to/esp-idf/components/soc/esp32/include/soc/mmu.h \ + --core-isa /path/to/xtensa-esp32-elf/include/xtensa/config/core-isa.h +python tests/vendor/xtensa_integer/run_flash_c.py \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld \ + --nm /path/to/xtensa-esp32-elf-nm +python tests/vendor/xtensa_integer/run_reset_c.py \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld \ + --nm /path/to/xtensa-esp32-elf-nm \ + --flash-worker +``` diff --git a/tests/unit/test_esp32_flashmap.c b/tests/unit/test_esp32_flashmap.c new file mode 100644 index 000000000..09ff3588b --- /dev/null +++ b/tests/unit/test_esp32_flashmap.c @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/fabric.h" +static uint32_t ctrl(unsigned cpu) { return 0x3ff00040u+cpu*24u; } +static uint32_t table(unsigned cpu,unsigned index) { return 0x3ff10000u+cpu*0x2000u+index*4u; } +static void enable(esk_xtensa_memory *bus,unsigned cpu) +{ + CHECK_EQ(bus->write(bus->ctx,ctrl(cpu),4,0),ES_OK); + CHECK_EQ(bus->write(bus->ctx,ctrl(cpu),4,16),ES_OK); + CHECK_EQ(bus->write(bus->ctx,ctrl(cpu),4,24),ES_OK); +} +static uint32_t pattern(uint32_t address) { return (address*13u^(address>>8)^(address>>16))&255u; } +static void full_mapping_and_masks(void) +{ + esk_alloc a;esk_esp32_fabric *f=NULL;esk_xtensa_memory bus[2];unsigned cpu,index,k;uint32_t i,v=0; + unsigned char *image=(unsigned char *)malloc(0x1000000);const uint32_t offsets[]={0x2000,0x7ffe,0xfffc,0xffff}; + CHECK(image!=NULL);if(!image) return; + for(i=0;i<0x1000000;i++) image[i]=(unsigned char)pattern(i); + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_fabric_create(&a,&f),ES_OK); + CHECK_EQ(esk_esp32_fabric_flash(f,image,0x1000000),ES_OK); + image[0]^=255; /* source changes cannot mutate the private backing */ + for(cpu=0;cpu<2;cpu++) { + bus[cpu]=esk_esp32_fabric_bus(f,cpu); + CHECK_EQ(bus[cpu].read(bus[cpu].ctx,ctrl(cpu),4,&v),ES_OK);CHECK_EQ(v,16); + CHECK_EQ(bus[cpu].read(bus[cpu].ctx,ctrl(cpu)+4u,4,&v),ES_OK);CHECK_EQ(v,0x8ff); + for(index=0;index<256;index++) { + CHECK_EQ(bus[0].read(bus[0].ctx,table(cpu,index),4,&v),ES_OK);CHECK_EQ(v,0x100); + CHECK_EQ(bus[0].write(bus[0].ctx,table(cpu,index),4,(index*17u+cpu*31u)&255u),ES_OK); + } + CHECK_EQ(bus[0].write(bus[0].ctx,ctrl(cpu)+4u,4,0x8e8),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,ctrl(cpu),4,24),ES_EUNSUPPORTED); + CHECK_EQ(bus[0].read(bus[0].ctx,ctrl(cpu),4,&v),ES_OK);CHECK_EQ(v,16); + enable(&bus[0],cpu); + CHECK_EQ(bus[0].read(bus[0].ctx,ctrl(cpu),4,&v),ES_OK);CHECK_EQ(v,56); + for(index=0;index<256;index++) { + uint32_t base=index<64 ? 0x3f400000u+index*65536u : 0x40000000u+(index-64u)*65536u; + uint32_t physical=((index*17u+cpu*31u)&255u)*65536u; + if(index>=64 && index<76) continue; /* on-chip area never redirected */ + for(k=0;k=76 && index<255) { + uint32_t next=(((index+1u)*17u+cpu*31u)&255u)*65536u; + CHECK_EQ(bus[cpu].fetch(bus[cpu].ctx,base+65535u,3,&v),ES_OK); + CHECK_EQ(v,pattern(physical+65535u)|(pattern(next)<<8)|(pattern(next+1u)<<16)); + } + } + for(index=0;index<4;index++) { + const uint32_t addresses[]={0x3f400000,0x400d0000,0x40400000,0x40800000}; + uint32_t bit=index ? 1u<<(index-1u) : 16u; + CHECK_EQ(bus[0].write(bus[0].ctx,ctrl(cpu)+4u,4,0x8e8u|bit),ES_OK); + CHECK_EQ(bus[cpu].read(bus[cpu].ctx,addresses[index],4,&v),ES_EUNSUPPORTED); + CHECK_EQ(bus[0].write(bus[0].ctx,ctrl(cpu)+4u,4,0x8e8),ES_OK); + } + CHECK_EQ(bus[cpu].fetch(bus[cpu].ctx,0x3f400000,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(bus[cpu].read(bus[cpu].ctx,0x400d0000,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(bus[cpu].read(bus[cpu].ctx,0x3f400001,4,&v),ES_EINVAL); + CHECK_EQ(bus[cpu].fetch(bus[cpu].ctx,0x40bfffff,2,&v),ES_EUNSUPPORTED); + CHECK_EQ(bus[cpu].fetch(bus[cpu].ctx,0x40070000,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(bus[cpu].fetch(bus[cpu].ctx,0x40000400,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(bus[cpu].fetch(bus[cpu].ctx,0x40060000,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(bus[cpu].write(bus[cpu].ctx,0x3f400000,4,0),ES_EUNSUPPORTED); + CHECK_EQ(bus[cpu].compare_exchange(bus[cpu].ctx,0x3f400000,0,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(bus[cpu].fetch(bus[cpu].ctx,ctrl(cpu),1,&v),ES_EUNSUPPORTED); + CHECK_EQ(bus[0].read(bus[0].ctx,table(cpu,77),4,&v),ES_OK);i=v; + CHECK_EQ(bus[0].write(bus[0].ctx,table(cpu,77),4,9),ES_EUNSUPPORTED); + CHECK_EQ(bus[0].read(bus[0].ctx,table(cpu,77),4,&v),ES_OK);CHECK_EQ(v,i); + CHECK_EQ(bus[0].write(bus[0].ctx,ctrl(cpu),4,0x438),ES_EUNSUPPORTED); + CHECK_EQ(bus[0].write(bus[0].ctx,ctrl(cpu)+4u,4,0x8c8),ES_EUNSUPPORTED); + CHECK_EQ(bus[0].write(bus[0].ctx,table(cpu,0),1,0),ES_EINVAL); + CHECK_EQ(bus[0].read(bus[0].ctx,table(cpu,0)+1u,4,&v),ES_EINVAL); + CHECK_EQ(bus[0].read(bus[0].ctx,table(cpu,256),4,&v),ES_EUNSUPPORTED); + } + CHECK_EQ(esk_esp32_fabric_flash(f,image,0x1000000),ES_EUNSUPPORTED); + CHECK_EQ(bus[0].write(bus[0].ctx,ctrl(0),4,16),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,table(0,77),4,9),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,table(0,77),4,0x200),ES_EUNSUPPORTED); + CHECK_EQ(bus[0].write(bus[0].ctx,ctrl(0),4,24),ES_EUNSUPPORTED); + enable(&bus[0],0); + CHECK_EQ(bus[0].fetch(bus[0].ctx,0x400d0000,1,&v),ES_OK);CHECK_EQ(v,pattern(9u*65536u)); + CHECK_EQ(bus[1].fetch(bus[1].ctx,0x400d0000,1,&v),ES_OK);CHECK_EQ(v,pattern(((77u*17u+31u)&255u)*65536u)); + CHECK_EQ(bus[0].write(bus[0].ctx,0x40080000,4,0xf03d),ES_OK); + CHECK_EQ(bus[1].fetch(bus[1].ctx,0x40080000,2,&v),ES_OK);CHECK_EQ(v,0xf03d); + CHECK_EQ(bus[1].fetch(bus[1].ctx,0x400c0000,1,&v),ES_EUNSUPPORTED); + CHECK_EQ(bus[0].fetch(bus[0].ctx,0x400c0000,1,&v),ES_OK);CHECK_EQ(v,0); + esk_esp32_fabric_destroy(f);free(image);CHECK_EQ(a.live_bytes,0); +} +static void bounds_and_replacement(void) +{ + esk_alloc a;esk_esp32_fabric *f=NULL;esk_xtensa_memory bus;uint32_t v=0;unsigned char *image=(unsigned char *)malloc(65536); + CHECK(image!=NULL);if(!image) return; + memset(image,0x55,65536);esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_fabric_create(&a,&f),ES_OK);bus=esk_esp32_fabric_bus(f,0); + CHECK_EQ(esk_esp32_fabric_flash(f,image,65535),ES_EINVAL); + CHECK_EQ(bus.read(bus.ctx,ctrl(0),4,&v),ES_EUNSUPPORTED); + CHECK_EQ(esk_esp32_fabric_flash(f,image,65536),ES_OK); + CHECK_EQ(bus.write(bus.ctx,table(0,0),4,0),ES_OK);CHECK_EQ(bus.write(bus.ctx,table(0,1),4,1),ES_OK); + CHECK_EQ(bus.write(bus.ctx,ctrl(0)+4u,4,0x8ef),ES_OK);enable(&bus,0); + CHECK_EQ(bus.read(bus.ctx,0x3f400000,4,&v),ES_OK);CHECK_EQ(v,0x55555555); + v=0xdeadbeef;CHECK_EQ(bus.read(bus.ctx,0x3f410000,4,&v),ES_EUNSUPPORTED);CHECK_EQ(v,0xdeadbeefu); + CHECK_EQ(esk_esp32_fabric_load(f,0,0x3f400000,image,4),ES_EUNSUPPORTED); + CHECK_EQ(bus.write(bus.ctx,ctrl(0),4,16),ES_OK);memset(image,0x66,65536); + CHECK_EQ(esk_esp32_fabric_flash(f,image,65536),ES_OK); + CHECK_EQ(bus.write(bus.ctx,ctrl(0),4,24),ES_EUNSUPPORTED);enable(&bus,0); + CHECK_EQ(bus.read(bus.ctx,0x3f400000,4,&v),ES_OK);CHECK_EQ(v,0x66666666); + esk_esp32_fabric_destroy(f);free(image);CHECK_EQ(a.live_bytes,0); +} +static void *limited_alloc(void *ctx,size_t n) { unsigned *left=(unsigned *)ctx;if(!*left) return NULL;(*left)--;return malloc(n); } +static void *limited_realloc(void *ctx,void *p,size_t n) { (void)ctx;return realloc(p,n); } +static void limited_free(void *ctx,void *p) { (void)ctx;free(p); } +static void allocation_failures(void) +{ + unsigned remaining=1;es_allocator_t hooks={limited_alloc,limited_realloc,limited_free,&remaining}; + esk_alloc a;esk_esp32_fabric *f=NULL;esk_xtensa_memory bus;uint32_t v=0;size_t before; + unsigned char *image=(unsigned char *)malloc(65536);CHECK(image!=NULL);if(!image) return;memset(image,0x77,65536); + esk_alloc_init(&a,&hooks);CHECK_EQ(esk_esp32_fabric_create(&a,&f),ES_OK);bus=esk_esp32_fabric_bus(f,0);before=a.live_bytes; + remaining=1;CHECK_EQ(esk_esp32_fabric_flash(f,image,65536),ES_ENOMEM);CHECK_EQ(a.live_bytes,before); + CHECK_EQ(bus.read(bus.ctx,ctrl(0),4,&v),ES_EUNSUPPORTED); + remaining=2;CHECK_EQ(esk_esp32_fabric_flash(f,image,65536),ES_OK);before=a.live_bytes; + remaining=0;CHECK_EQ(esk_esp32_fabric_flash(f,image,65536),ES_ENOMEM);CHECK_EQ(a.live_bytes,before); + CHECK_EQ(bus.write(bus.ctx,table(0,0),4,0),ES_OK);CHECK_EQ(bus.write(bus.ctx,ctrl(0)+4u,4,0x8ef),ES_OK);enable(&bus,0); + CHECK_EQ(bus.read(bus.ctx,0x3f400000,4,&v),ES_OK);CHECK_EQ(v,0x77777777); + CHECK_EQ(bus.write(bus.ctx,ctrl(0),4,16),ES_OK);before=a.live_bytes; + CHECK_EQ(esk_esp32_fabric_flash(f,image,65536),ES_ENOMEM);CHECK_EQ(a.live_bytes,before); + /* Failed replacement cannot dirty an already flushed mapping. */ + CHECK_EQ(bus.write(bus.ctx,ctrl(0),4,24),ES_OK); + CHECK_EQ(bus.read(bus.ctx,0x3f400000,4,&v),ES_OK);CHECK_EQ(v,0x77777777); + esk_esp32_fabric_destroy(f);free(image);CHECK_EQ(a.live_bytes,0); +} +TEST_MAIN_BEGIN + (void)t_str_eq;RUN(full_mapping_and_masks);RUN(bounds_and_replacement);RUN(allocation_failures); +TEST_MAIN_END diff --git a/tests/vendor/README.md b/tests/vendor/README.md index ad127b852..4af133ae3 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -131,3 +131,18 @@ calls a compiled worker: 128 cycles, 128 static ROM entries, 16 exact replays, 11,904 retired instructions. No Python memory callback or host APP PC shortcut. This is not Espressif ROM, full reset/MMU/cache, SDK or board boot qualification. See [reset/vector scope](../../docs/engineering/esp32-reset-vectors.md). + +`xtensa_integer/run_flashmap.py` takes `--dport-reg`, `--soc-header`, `--core-isa` +and the pinned `--mmu-header`: 488 parsed-vendor translation comparisons, six +initial-control/flush comparisons and per-window mask checks. `run_flash_c.py` +takes the actual ESP32 compiler/objcopy/linker/nm paths: 256 compiled workers +execute different physical code/data at identical virtual addresses on the two +cores (64 exact replays, 11,264 retired instructions, 2,048 from flash). + +Add `--flash-worker` to `run_reset_c.py` for compiled PRO MMU/cache setup followed +by original test-ROM handoff into a mapped worker: 128 cycles, 16 replays, +15,360 retired instructions (1,664 from flash). Neither path uses Python memory +callbacks. These checks qualify immutable PID0/1 mappings with disabled-cache +edits and explicit flush; they do not qualify physical flash/SPI, live remap, +cache tags/timing or real ROM/SDK boot. See the +[flash profile contract](../../docs/engineering/esp32-flash-mapping.md). diff --git a/tests/vendor/xtensa_integer/run_controls.py b/tests/vendor/xtensa_integer/run_controls.py index e76e69998..e66acbcf7 100644 --- a/tests/vendor/xtensa_integer/run_controls.py +++ b/tests/vendor/xtensa_integer/run_controls.py @@ -38,7 +38,7 @@ def main(): with tempfile.TemporaryDirectory(prefix='embsim-controls-') as tmp: lib = Path(tmp) / 'controls.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_flash_c.py b/tests/vendor/xtensa_integer/run_flash_c.py new file mode 100644 index 000000000..19a96bfe3 --- /dev/null +++ b/tests/vendor/xtensa_integer/run_flash_c.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""ESP32-compiled IRAM setup calls code through per-core flash MMU mappings. + +Identical virtual addresses map to different physical code/data on the two +cores. Native control/table/flush access only; no Python memory callbacks. +Not SPI programming, cache tag/timing, high-PID, ROM/SDK or full board proof. +""" +import argparse +import ctypes as C +from pathlib import Path +import random +import subprocess +import tempfile +from run_execute import ROOT, Memory, Fault +from run_windows_c import Windows +from run_machine_c import Allocator + +BOOTSTRAP = ''' +unsigned bootstrap(unsigned bank,unsigned codepage,unsigned datapage,unsigned x,unsigned entry) { + volatile unsigned *mmu=(volatile unsigned *)(0x3ff10000+bank*0x2000); + volatile unsigned *ctrl=(volatile unsigned *)(0x3ff00040+bank*24); + volatile unsigned *masks=ctrl+1; + *ctrl=0; + mmu[0]=datapage; + mmu[77]=codepage; + *masks=*masks&~17u; + *ctrl=16; + while(!(*ctrl&32)) { } + *ctrl=24; + return ((unsigned (*)(unsigned))entry)(x); +} +''' +WORKER = ''' +unsigned flash_worker(unsigned x) { + volatile const unsigned *data=(volatile const unsigned *)0x3f400100; + return (x^data[0])+CODETAG; +} +''' + + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + for name in ['compiler', 'objcopy', 'linker', 'nm']: + ap.add_argument('--' + name, required=True) + ap.add_argument('--cc', default='cc'); args = ap.parse_args(); rng = random.Random(0xf1a5) + with tempfile.TemporaryDirectory(prefix='embsim-flash-c-') as tmp: + out = Path(tmp) + def compile_code(name, source, base, entry, flags=()): + src, obj, elf, binary = [out / (name + '.' + ext) for ext in ['c', 'o', 'elf', 'bin']] + src.write_text(source, encoding='utf-8') + subprocess.run([args.compiler, '-B' + str(Path(args.compiler).parent) + '/', '-O1', '-ffreestanding', + '-fno-builtin', '-mtext-section-literals', *flags, '-c', str(src), '-o', str(obj)], check=True) + subprocess.run([args.linker, '--no-relax', '-Ttext=' + hex(base), '--entry=' + entry, str(obj), '-o', str(elf)], check=True) + subprocess.run([args.objcopy, '-O', 'binary', '--only-section=.text', str(elf), str(binary)], check=True) + symbols = {f[2]: int(f[0], 16) for line in subprocess.check_output([args.nm, str(elf)], text=True).splitlines() + if len(f := line.split()) == 3} + return binary.read_bytes(), symbols[entry] + bootstrap, start = compile_code('bootstrap', BOOTSTRAP, 0x40080000, 'bootstrap') + tags = {1: 0x12345678, 3: 0x87654321}; data = {2: 0xa0123456, 4: 0xb0987654} + image = bytearray(b'\xa5' * (6 * 65536)); entry = None + for page, tag in tags.items(): + code, address = compile_code('worker' + str(page), WORKER, 0x400d0000, 'flash_worker', ['-DCODETAG=' + hex(tag) + 'u']) + assert len(code) < 65536 and (entry is None or entry == address) + entry = address; image[page * 65536:page * 65536 + len(code)] = code + for page, value in data.items(): image[page * 65536 + 256:page * 65536 + 260] = value.to_bytes(4, 'little') + flash_image = bytes(image) + lib = out / 'flash.so' + sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c']] + sources += [ROOT / 'core/util/alloc.c'] + subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', + '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) + api = C.CDLL(str(lib)); pointer = C.c_void_p + def bind(name, result, *params): + fn = getattr(api, name); fn.restype = result; fn.argtypes = list(params); return fn + alloc_init = bind('esk_alloc_init', None, C.POINTER(Allocator), pointer) + create = bind('esk_esp32_machine_create', C.c_int, C.POINTER(Allocator), C.POINTER(pointer)) + destroy = bind('esk_esp32_machine_destroy', None, pointer) + fabric = bind('esk_esp32_machine_fabric', pointer, pointer) + bus_get = bind('esk_esp32_machine_bus', Memory, pointer, C.c_uint) + load = bind('esk_esp32_fabric_load', C.c_int, pointer, C.c_uint, C.c_uint32, pointer, C.c_size_t) + install = bind('esk_esp32_fabric_flash', C.c_int, pointer, pointer, C.c_size_t) + configure = bind('esk_esp32_machine_configure', C.c_int, pointer, C.c_uint, C.c_uint32) + core = bind('esk_esp32_machine_core', C.POINTER(Windows), pointer, C.c_uint) + run = bind('esk_esp32_machine_run_core', C.c_int, pointer, C.c_uint, C.c_int) + step = bind('esk_esp32_machine_step', C.c_int, pointer, C.c_uint, C.POINTER(C.c_uint), C.POINTER(Fault)) + retired = bind('esk_esp32_machine_retired', C.c_uint64, pointer, C.c_uint) + total = 0; workers = 0; flash_instructions = 0 + for trial in range(64): + inputs = [rng.getrandbits(32) for _ in range(2)] + codepages = [1, 3] if trial % 2 else [3, 1] + datapages = [2, 4] if trial % 4 < 2 else [4, 2] + expected = [((x ^ data[dp]) + tags[cp]) & 0xffffffff for x, cp, dp in zip(inputs, codepages, datapages)] + replay_trace = None + for replay in range(2): + allocator = Allocator(); alloc_init(C.byref(allocator), None) + machine = pointer(); assert create(C.byref(allocator), C.byref(machine)) == 0 + try: + ram = fabric(machine); bus = bus_get(machine, 0) + assert install(ram, C.c_char_p(flash_image), len(flash_image)) == 0 + assert load(ram, 0, 0x40080000, C.c_char_p(bootstrap), len(bootstrap)) == 0 + assert bus.write(bus.ctx, 0x3ff00030, 4, 1) == 0 + states = [] + for cpu in range(2): + assert configure(machine, cpu, start) == 0 + w = core(machine, cpu).contents; w.ps |= 1 << 16 + w.cpu.a[0] = 0x12340000 + cpu; w.cpu.a[1] = 0x3ffd0000 + cpu * 4096; w.cpu.a[4] = 0x7ffffff0 + for n, arg in enumerate([cpu, codepages[cpu], datapages[cpu], inputs[cpu], entry]): w.cpu.a[6 + n] = arg + states.append(w); assert run(machine, cpu, 1) == 0 + done = [False, False]; event = C.c_uint(); fault = Fault(); trace = []; fetched = [0, 0] + for turn in range(10000): + cpu = (turn + trial) % 2; w = states[cpu] + if done[cpu]: continue + if w.cpu.pc == 0x7ffffff0: + done[cpu] = True; assert run(machine, cpu, 0) == 0 + if all(done): break + continue + address = w.cpu.pc + status = step(machine, cpu, C.byref(event), C.byref(fault)) + assert status == 0 and event.value == 0, (status, cpu, hex(fault.pc), hex(fault.opcode), fault.kind, event.value) + if 0x400d0000 <= address < 0x400e0000: fetched[cpu] += 1; flash_instructions += 1 + trace.append((cpu, address, w.cpu.pc)); total += 1 + assert all(done) and all(fetched) + value = C.c_uint32() + for cpu, w in enumerate(states): + assert w.cpu.a[6] == expected[cpu] and w.base == 0 and w.start == 1 + assert w.cpu.a[0] == 0x12340000 + cpu and w.cpu.a[1] == 0x3ffd0000 + cpu * 4096 + assert retired(machine, cpu) == sum(owner == cpu for owner, _, _ in trace) + for address, wanted in [(0x3ff10000 + cpu * 0x2000, datapages[cpu]), + (0x3ff10000 + cpu * 0x2000 + 77 * 4, codepages[cpu]), + (0x3ff00040 + cpu * 24, 56)]: + assert bus.read(bus.ctx, address, 4, C.byref(value)) == 0 and value.value == wanted + result = (trace, [w.cpu.a[6] for w in states]) + if replay: assert result == replay_trace + else: replay_trace = result + workers += 2 + finally: + destroy(machine) + assert allocator.live_bytes == 0 + print(f'{workers} mapped-flash C workers, 64 exact replays, {total} retired instructions ({flash_instructions} fetched from mapped flash)') + + +if __name__ == '__main__': + main() diff --git a/tests/vendor/xtensa_integer/run_flashmap.py b/tests/vendor/xtensa_integer/run_flashmap.py new file mode 100644 index 000000000..e753da9b0 --- /dev/null +++ b/tests/vendor/xtensa_integer/run_flashmap.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""Compare the read-only PID0/1 flash profile against pinned ESP32 header facts. + +Reference addresses, fields and bus windows are parsed, not vendor code linked. +No physical cache/SPI, live-remap or complete SDK boot qualification. +""" +import argparse +import ctypes as C +from pathlib import Path +import re +import subprocess +import tempfile +from run_execute import ROOT +from run_machine_c import Allocator +from run_matrix import read_header + + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + for name in ['dport-reg', 'soc-header', 'core-isa', 'mmu-header']: + ap.add_argument('--' + name, type=Path, required=True) + ap.add_argument('--cc', default='cc'); args = ap.parse_args() + dport = read_header(args.dport_reg, '38deaa4ef02016ee9cdf490a9bfe4b587f11d1d1e6a2329dc858a50b9b248817') + soc = read_header(args.soc_header, '7a7148c288d7c289c961810e5617fa7b14d105b28349a45f772150b1e0845dd3') + core = read_header(args.core_isa, '60d20f76c7730b24041b9f55b16e0062f30bf85c7661c7ed215f050fedcdb344') + mmu = read_header(args.mmu_header, '59842853da8931e4a5fd61ec0cad565a38b691d53a96110439d9d0aadaf920b9') + def number(source, name): + return int(re.search(r'#define\s+' + name + r'\s+(0x[0-9A-Fa-f]+|\d+)\b', source)[1], 0) + base = number(soc, 'DR_REG_DPORT_BASE') + def register(name): + return base + int(re.search(r'#define\s+' + name + r'\s+\(DR_REG_DPORT_BASE\s*\+\s*(0x[0-9A-Fa-f]+)\)', dport)[1], 16) + def initial(name): + start = re.search(r'^#define\s+' + name + r'\s+\(DR_REG_DPORT_BASE[^\n]*', dport, re.M).end() + following = re.search(r'^#define\s+DPORT_\w+_REG\s+\(DR_REG_DPORT_BASE', dport[start:], re.M) + text = dport[start:start + following.start()] if following else dport[start:] + value = 0 + for high, low, radix, digits in re.findall(r";bitpos:\[(\d+)(?::(\d+))?\] ;default: \d+'([bhd])([0-9a-fA-F]+)", text): + low = int(low or high); value |= int(digits, {'b': 2, 'h': 16, 'd': 10}[radix]) << low + return value + invalid = number(dport, 'DPORT_FLASH_MMU_TABLE_INVALID_VAL') + ppn_mask = number(dport, 'DPORT_MMU_ADDRESS_MASK') + pages = number(mmu, 'SOC_MMU_PAGES_PER_REGION') + first = [number(mmu, 'SOC_MMU_DROM0_PAGES_START')] + [number(mmu, 'SOC_MMU_IROM0_PAGES_START') + pages * n for n in range(3)] + windows = [number(core, 'XCHAL_' + region + '_VADDR') for region in ['DATAROM0', 'INSTRAM0', 'INSTRAM1', 'INSTROM0']] + internal_end = number(soc, 'SOC_RTC_IRAM_HIGH') + image = b''.join(bytes(byte ^ page for byte in range(256)) * 256 for page in range(ppn_mask + 1)) + assert len(image) == 16 * 1024 * 1024 and pages == 64 + compared = 0; fields = 0 + with tempfile.TemporaryDirectory(prefix='embsim-flashmap-') as tmp: + lib = Path(tmp) / 'flashmap.so' + subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', + '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), str(ROOT / 'core/mcu/esp32/flashmap.c'), + str(ROOT / 'core/util/alloc.c'), '-o', str(lib)], check=True) + api = C.CDLL(str(lib)); pointer = C.c_void_p + def bind(name, result, *params): + fn = getattr(api, name); fn.restype = result; fn.argtypes = list(params); return fn + alloc_init = bind('esk_alloc_init', None, C.POINTER(Allocator), pointer) + create = bind('esk_esp32_flashmap_create', C.c_int, C.POINTER(Allocator), C.POINTER(pointer)) + install = bind('esk_esp32_flashmap_install', C.c_int, pointer, pointer, C.c_size_t) + destroy = bind('esk_esp32_flashmap_destroy', None, pointer) + read = bind('esk_esp32_flashmap_reg_read', C.c_int, pointer, C.c_uint32, C.c_uint, C.POINTER(C.c_uint32)) + write = bind('esk_esp32_flashmap_reg_write', C.c_int, pointer, C.c_uint32, C.c_uint, C.c_uint32) + access = bind('esk_esp32_flashmap_read', C.c_int, pointer, C.c_uint, C.c_uint32, C.c_uint, C.c_int, C.POINTER(C.c_uint32)) + allocator = Allocator(); alloc_init(C.byref(allocator), None) + flash = pointer(); assert create(C.byref(allocator), C.byref(flash)) == 0 + try: + assert install(flash, C.c_char_p(image), len(image)) == 0 + value = C.c_uint32() + for cpu, prefix in enumerate(['PRO', 'APP']): + control_name, masks_name = [f'DPORT_{prefix}_CACHE_{suffix}_REG' for suffix in ['CTRL', 'CTRL1']] + control, masks = register(control_name), register(masks_name) + table = number(soc, 'DR_REG_FLASH_MMU_TABLE_' + prefix) + enable = 1 << number(dport, f'DPORT_{prefix}_CACHE_ENABLE_S') + flush = 1 << number(dport, f'DPORT_{prefix}_CACHE_FLUSH_ENA_S') + done = 1 << number(dport, f'DPORT_{prefix}_CACHE_FLUSH_DONE_S') + mask_bits = [1 << number(dport, f'DPORT_{prefix}_CACHE_MASK_{region}_S') for region in ['DROM0', 'IRAM0', 'IRAM1', 'IROM0']] + for address, expected in [(control, initial(control_name)), (masks, initial(masks_name))]: + assert read(flash, address, 4, C.byref(value)) == 0 and value.value == expected, (control_name, hex(address), hex(value.value), hex(expected)); fields += 1 + assert write(flash, control, 4, 0) == 0 + for index in range(4 * pages): + assert read(flash, table + index * 4, 4, C.byref(value)) == 0 and value.value == invalid + assert write(flash, table + index * 4, 4, (index * 7 + cpu * 11) & ppn_mask) == 0 + unmasked = initial(masks_name) & ~sum(mask_bits) + assert write(flash, masks, 4, unmasked) == 0 + assert write(flash, control, 4, flush) == 0 + assert read(flash, control, 4, C.byref(value)) == 0 and value.value == flush | done; fields += 1 + assert write(flash, control, 4, flush | enable) == 0 + for region, virtual in enumerate(windows): + for page in range(pages): + address = virtual + page * 65536 + 65535 + if region == 1 and address < internal_end: continue + physical_page = ((first[region] + page) * 7 + cpu * 11) & ppn_mask + assert access(flash, cpu, address, 1, region != 0, C.byref(value)) == 0 + assert value.value == 255 ^ physical_page + compared += 1 + probe = virtual + (13 * 65536 if region == 1 else 0) + assert write(flash, masks, 4, unmasked | mask_bits[region]) == 0 + assert access(flash, cpu, probe, 1, region != 0, C.byref(value)) != 0 + assert write(flash, masks, 4, unmasked) == 0 + finally: + destroy(flash) + assert allocator.live_bytes == 0 + print(f'{compared} vendor-window/page translations and {fields} parsed control/default/flush comparisons pass') + + +if __name__ == '__main__': + main() diff --git a/tests/vendor/xtensa_integer/run_ipi_c.py b/tests/vendor/xtensa_integer/run_ipi_c.py index 8275ce4ec..7c2d00e0b 100644 --- a/tests/vendor/xtensa_integer/run_ipi_c.py +++ b/tests/vendor/xtensa_integer/run_ipi_c.py @@ -92,7 +92,7 @@ def main(): if len(f := line.split()) == 3} lib = out / 'ipi.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_machine_c.py b/tests/vendor/xtensa_integer/run_machine_c.py index 37c194994..ffc49c86d 100644 --- a/tests/vendor/xtensa_integer/run_machine_c.py +++ b/tests/vendor/xtensa_integer/run_machine_c.py @@ -59,7 +59,7 @@ def main(): if len(f := line.split()) == 3} lib = out / 'machine.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), diff --git a/tests/vendor/xtensa_integer/run_reset_c.py b/tests/vendor/xtensa_integer/run_reset_c.py index 0a58e9f63..4860a742e 100644 --- a/tests/vendor/xtensa_integer/run_reset_c.py +++ b/tests/vendor/xtensa_integer/run_reset_c.py @@ -26,6 +26,16 @@ *reset=1; *clock=1; *stall=0; +#if MAPPED_FLASH + volatile unsigned *cache=(volatile unsigned *)0x3ff00058; + volatile unsigned *mmu=(volatile unsigned *)0x3ff12000; + *cache=0; + mmu[77]=0; + cache[1]=cache[1]&~1u; + *cache=16; + while(!(*cache&32)) { } + *cache=24; +#endif mailbox[0]=token; mailbox[1]=0; *address=boot; @@ -80,7 +90,7 @@ def main(): ap = argparse.ArgumentParser(description=__doc__) for name in ['compiler', 'objcopy', 'linker', 'nm']: ap.add_argument('--' + name, required=True) - ap.add_argument('--cc', default='cc'); args = ap.parse_args() + ap.add_argument('--cc', default='cc'); ap.add_argument('--flash-worker', action='store_true'); args = ap.parse_args() with tempfile.TemporaryDirectory(prefix='embsim-reset-c-') as tmp: out = Path(tmp) def compile_image(name, source, base, entry, assembly=False): @@ -88,7 +98,7 @@ def compile_image(name, source, base, entry, assembly=False): elf, binary = out / (name + '.elf'), out / (name + '.bin') src.write_text(source, encoding='utf-8') subprocess.run([args.compiler, '-B' + str(Path(args.compiler).parent) + '/', '-O1', - '-mtext-section-literals', '-ffreestanding', '-fno-builtin', '-c', str(src), '-o', str(obj)], check=True) + '-DMAPPED_FLASH=' + str(int(args.flash_worker)), '-mtext-section-literals', '-ffreestanding', '-fno-builtin', '-c', str(src), '-o', str(obj)], check=True) subprocess.run([args.linker, '--no-relax', '-Ttext=' + hex(base), '--entry=' + entry, str(obj), '-o', str(elf)], check=True) subprocess.run([args.objcopy, '-O', 'binary', '--only-section=.text', str(elf), str(binary)], check=True) @@ -96,12 +106,18 @@ def compile_image(name, source, base, entry, assembly=False): if len(f := line.split()) == 3} return binary.read_bytes(), symbols code, symbols = compile_image('tasks', SOURCE, 0x40080000, 'supervisor') + flash_image = None + if args.flash_worker: + worker_source = 'unsigned worker(void)' + SOURCE.split('unsigned worker(void)', 1)[1] + worker, flash_symbols = compile_image('flashworker', worker_source, 0x400d0000, 'worker') + assert len(worker) < 65536 + flash_image = worker + bytes(65536 - len(worker)); symbols['worker'] = flash_symbols['worker'] rom, rom_symbols = compile_image('rom', ROM, 0x40000000, 'reset_entry', True) assert rom_symbols['reset_entry'] == 0x40000400 and len(rom) < 0x60000 rom = rom + bytes(0x60000 - len(rom)) # original test fixture, not a product ROM lib = out / 'reset.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) @@ -114,6 +130,7 @@ def bind(name, result, *params): fabric = bind('esk_esp32_machine_fabric', pointer, pointer) bus_get = bind('esk_esp32_machine_bus', Memory, pointer, C.c_uint) load = bind('esk_esp32_fabric_load', C.c_int, pointer, C.c_uint, C.c_uint32, pointer, C.c_size_t) + install_flash = bind('esk_esp32_fabric_flash', C.c_int, pointer, pointer, C.c_size_t) install_rom = bind('esk_esp32_fabric_rom', C.c_int, pointer, C.c_uint, pointer, C.c_size_t) configure = bind('esk_esp32_machine_configure', C.c_int, pointer, C.c_uint, C.c_uint32) reset_profile = bind('esk_esp32_machine_app_reset_configure', C.c_int, pointer, C.c_uint) @@ -122,7 +139,7 @@ def bind(name, result, *params): run = bind('esk_esp32_machine_run_core', C.c_int, pointer, C.c_uint, C.c_int) step = bind('esk_esp32_machine_step', C.c_int, pointer, C.c_uint, C.POINTER(C.c_uint), C.POINTER(Fault)) pending = bind('esk_xtensa_irq_pending', C.c_uint32, C.POINTER(Windows)) - total = 0; cycles = 0; rom_entries = 0; held = 0 + total = 0; cycles = 0; rom_entries = 0; held = 0; flash_fetches = 0 for trial in range(16): replay_trace = None for replay in range(2): @@ -132,6 +149,7 @@ def bind(name, result, *params): ram = fabric(machine); bus = bus_get(machine, 0); value = C.c_uint32() assert load(ram, 0, 0x40080000, C.c_char_p(code), len(code)) == 0 assert install_rom(ram, 0, C.c_char_p(rom), len(rom)) == 0 + if flash_image is not None: assert install_flash(ram, C.c_char_p(flash_image), len(flash_image)) == 0 assert reset_profile(machine, 1) == 0 and run(machine, 1, 1) == 0 app = core(machine, 1).contents; app.irq.intenable = 0xffffffff # A live peripheral source must survive CPU-only resets. ROM @@ -162,6 +180,7 @@ def bind(name, result, *params): assert app.cpu.pc == 0x40000400 and app.vecbase == 0x40000000 assert app.ps == 0x1f and app.lcount == 0 entered = True; rom_entries += 1 + if cpu == 1 and 0x400d0000 <= app.cpu.pc < 0x400e0000: flash_fetches += 1 status = step(machine, cpu, C.byref(event), C.byref(fault)) assert status == 0 and event.value in [0, 18, 20, 22], (status, cpu, hex(fault.pc), hex(fault.opcode), event.value) total += event.value == 0; held += event.value == 22 @@ -185,7 +204,8 @@ def bind(name, result, *params): destroy(machine) assert allocator.live_bytes == 0 assert held > 0 - print(f'{cycles} compiled reset/worker cycles, {rom_entries} static test-ROM entries, 16 exact replays, {total} retired instructions; {held} reset-held turns') + if args.flash_worker: assert flash_fetches > 0 + print(f'{cycles} compiled reset/worker cycles, {rom_entries} static test-ROM entries, 16 exact replays, {total} retired instructions; {held} reset-held turns; {flash_fetches} mapped-flash instructions') if __name__ == '__main__': From 8321847894692322ca71dc6a3c1d2c0b79c1507d Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 04:49:21 +0000 Subject: [PATCH 20/57] Implement Xtensa task registers and compiler TLS acceptance --- CHANGELOG.md | 10 ++ core/mcu/xtensa/windows.c | 23 ++- core/mcu/xtensa/windows.h | 8 + docs/STATUS.md | 8 + docs/engineering/esp32-context-registers.md | 106 ++++++++++++ docs/engineering/esp32-dual-core.md | 11 ++ tests/unit/test_xtensa_context.c | 76 ++++++++ tests/vendor/README.md | 9 + tests/vendor/xtensa_integer/run_context.py | 77 +++++++++ tests/vendor/xtensa_integer/run_tls_c.py | 172 +++++++++++++++++++ tests/vendor/xtensa_integer/run_windows_c.py | 2 +- 11 files changed, 497 insertions(+), 5 deletions(-) create mode 100644 docs/engineering/esp32-context-registers.md create mode 100644 tests/unit/test_xtensa_context.c create mode 100644 tests/vendor/xtensa_integer/run_context.py create mode 100644 tests/vendor/xtensa_integer/run_tls_c.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fb1921fb..bfa497706 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental ESP32 thread-local storage execution + +- Added unprivileged RUR/WUR.THREADPTR and privileged RSR/WSR/XSR.MISC0..3, + with full-width, per-core storage and correct operand/window checks. +- Added read-only PRID with an explicit fixture input; unconfigured reads remain + unsupported. No guessed silicon identity, scheduler or real SDK boot is added. +- Added 9,723 native context assertions, 7,680 independent assembler comparisons and actual + ESP32-compiled C TLS acceptance: 1,536 calls across six persistent banks, + 16 exact replays and 52,224 retired instructions through native RAM. + ### Experimental ESP32 read-only mapped flash - Added copied whole-page flash backing, per-core PID0/1 normal-mode MMU tables, diff --git a/core/mcu/xtensa/windows.c b/core/mcu/xtensa/windows.c index 3c07bbb7e..5eb4431a2 100644 --- a/core/mcu/xtensa/windows.c +++ b/core/mcu/xtensa/windows.c @@ -8,7 +8,7 @@ enum execution_kind { XT_BASE, XT_CALL, XT_ENTRY, XT_RETW, XT_WINDOW_RETURN, XT_ROTW, XT_SPECIAL, XT_RSYNC, XT_LOOP, XT_EXCEPTION_MEMORY, XT_SAR_SETUP=11, XT_SAR_SHIFT, XT_FENCE, XT_RSIL, XT_SYSCALL, - XT_EXCEPTION_RETURN, XT_ILLEGAL, XT_RFI, XT_WAITI, XT_ATOMIC, XT_DIVIDE + XT_EXCEPTION_RETURN, XT_ILLEGAL, XT_RFI, XT_WAITI, XT_ATOMIC, XT_DIVIDE, XT_THREADPTR }; static uint32_t bit(unsigned n) { return UINT32_C(1) << (n & 15u); } @@ -31,6 +31,11 @@ void esk_xtensa_windows_init(esk_xtensa_windows *w, uint32_t pc, const uint32_t memset(w,0,sizeof *w); w->cpu.pc=pc; w->ps=ESK_XT_PS_WOE; w->start=1; if (vectors) memcpy(w->vectors,vectors,sizeof w->vectors); } +es_status esk_xtensa_prid_configure(esk_xtensa_windows *w,uint32_t value) +{ + if(!w) return ES_EINVAL; + w->prid=value;w->prid_enabled=1;return ES_OK; +} es_status esk_xtensa_vecbase_configure(esk_xtensa_windows *w,uint32_t base) { unsigned i;static const uint32_t general[3]={0x300,0x340,0x3c0}; @@ -78,19 +83,21 @@ es_status esk_xtensa_raise_exception(esk_xtensa_windows *w, unsigned cause, } static int special_valid(const esk_xtensa_windows *w,unsigned reg) { - return (w->irq.enabled && esk_xtensa_irq_special(reg)) || reg<=3 || reg==12 || reg==72 || reg==73 || reg==177 || reg==192 || reg==230 || (reg==231 && w->vecbase_enabled) || reg==232 || reg==238; + return (w->irq.enabled && esk_xtensa_irq_special(reg)) || reg<=3 || reg==12 || reg==72 || reg==73 || reg==177 || reg==192 || reg==230 || (reg==231 && w->vecbase_enabled) || reg==232 || reg==238 || (reg>=244 && reg<=247) || (reg==235 && w->prid_enabled); } static uint32_t special_read(const esk_xtensa_windows *w,unsigned reg) { + if(reg>=244 && reg<=247) return w->misc[reg-244]; if (esk_xtensa_irq_special(reg)) { uint32_t value=0; (void)esk_xtensa_irq_read(w,reg,&value); return value; } switch (reg) { case 0:return w->lbeg; case 1:return w->lend; case 2:return w->lcount; case 3:return w->sar; case 12:return w->scompare1; case 72:return w->base; case 73:return w->start; case 177:return w->epc1; - case 192:return w->depc; case 230:return w->ps&ESK_XT_PS_MASK; case 231:return w->vecbase; case 232:return w->exccause; default:return w->excvaddr; + case 192:return w->depc; case 230:return w->ps&ESK_XT_PS_MASK; case 235:return w->prid; case 231:return w->vecbase; case 232:return w->exccause; default:return w->excvaddr; } } static void special_write(esk_xtensa_windows *w,unsigned reg,uint32_t value) { + if(reg>=244 && reg<=247) { w->misc[reg-244]=value;return; } if (esk_xtensa_irq_special(reg)) { (void)esk_xtensa_irq_write(w,reg,value); return; } switch (reg) { case 0:w->lbeg=value;break; case 1:w->lend=value;break; case 2:w->lcount=value;break; @@ -172,10 +179,15 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo else if ((word&UINT32_C(0xffff0f))==0x408000) kind=XT_ROTW; else if ((word&UINT32_C(0xff000f))==0x030000 || (word&UINT32_C(0xff000f))==0x130000 || (word&UINT32_C(0xff000f))==0x610000) { unsigned reg=(word>>8)&255u; - if (!special_valid(&next,reg) || (reg==227 && (word&UINT32_C(0xff000f))==0x030000) || ((word&UINT32_C(0xff000f))==0x610000 && reg!=3 && reg!=12 && reg!=230 && reg!=231)) + if (!special_valid(&next,reg) || (reg==227 && (word&UINT32_C(0xff000f))==0x030000) || ((word&UINT32_C(0xff000f))==0x610000 && reg!=3 && reg!=12 && reg!=230 && reg!=231 && !(reg>=244 && reg<=247)) || (reg==235 && (word&UINT32_C(0xff000f))!=0x030000)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); kind=XT_SPECIAL; mask=bit(t); } + else if ((word&UINT32_C(0xff000f))==0xe30000 && ((word>>4)&255u)==231) { + kind=XT_THREADPTR;n=0;mask=bit(r); + } else if ((word&UINT32_C(0xff000f))==0xf30000 && ((word>>8)&255u)==231) { + kind=XT_THREADPTR;n=1;mask=bit(t); + } else if (op==6 && t==7 && ((word>>12)&15u)>=8 && ((word>>12)&15u)<=10) { kind=XT_LOOP; mask=bit(s); } else if ((word&UINT32_C(0xff000f))==0x090000 || (word&UINT32_C(0xff000f))==0x490000) { if (!(next.ps&ESK_XT_PS_EXCM)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); @@ -245,6 +257,9 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo special_write(&next,reg,value);if(form==0x610000) next.cpu.a[t]=old; } next.cpu.pc=pc+3; + } else if (kind==XT_THREADPTR) { + if(n) next.threadptr=next.cpu.a[t];else next.cpu.a[r]=next.threadptr; + next.cpu.pc=pc+3; } else if (kind==XT_LOOP) { unsigned condition=(word>>12)&15u; uint32_t count=next.cpu.a[s]; next.lcount=count-1u; next.lbeg=pc+3; next.lend=pc+4+(word>>16); diff --git a/core/mcu/xtensa/windows.h b/core/mcu/xtensa/windows.h index 7693573ac..47d044ed6 100644 --- a/core/mcu/xtensa/windows.h +++ b/core/mcu/xtensa/windows.h @@ -25,6 +25,7 @@ typedef struct esk_xtensa_windows { esk_xtensa_interrupts irq; uint32_t vectors[6]; /* overflow4, underflow4, overflow8, underflow8, overflow12, underflow12 */ uint32_t vecbase,vecbase_enabled; + uint32_t threadptr,misc[4],prid,prid_enabled; } esk_xtensa_windows; /* event 0: retired; 1..6: window transition; 7/8/9: kernel/user/double exception; * 11..17: accepted interrupt level 1..7; 18: suspended WAITI (step only). @@ -32,6 +33,13 @@ typedef struct esk_xtensa_windows { * syscall/illegal handlers explicitly decide whether to advance EPC1/DEPC. * Architectural exception entries intentionally update saved state and PC. * Other failures retain state and return the ordinary fault report. */ +/* THREADPTR UR231 and MISC0..3 SR244..247 are per-core, full-width state. + * Init zeros them only as fixture setup; supported CPU reset preserves their + * undefined architectural values as the declared deterministic policy. + * PRID SR235 is read-only and unsupported until explicit reset-pin fixture + * configuration. This supplies a constant board/SoC input, not a guessed ESP32 + * silicon ID or a live pin-waveform model; it survives supported CPU reset. */ +es_status esk_xtensa_prid_configure(esk_xtensa_windows *state,uint32_t value); /* Explicit ESP32 relocatable-vector profile. Only 1024-aligned bases are * qualified; unaligned/lock-bit writes fail unchanged rather than guessing * silicon masking. Aligned RSR/WSR/XSR.VECBASE use SR231 when enabled. diff --git a/docs/STATUS.md b/docs/STATUS.md index c5d043276..b1f084ecc 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -100,6 +100,14 @@ Compiled PRO setup plus original test-ROM handoff also passes 128 reset cycles into a mapped worker. Immutable backing and disabled-cache table edits bound this profile; SPI programming, real cache tags/timing, live SDK mmap/invalidation, PSRAM/high PIDs and actual ROM/SDK/board boot remain unimplemented. +The [CPU task-context register profile](engineering/esp32-context-registers.md) +now implements unprivileged THREADPTR, privileged MISC0..3 and read-only PRID +with an explicitly supplied fixture constant. PRID's full silicon value is still +unverified; the SDK's bit-13 core extraction is tested without inventing it. +Evidence: 9,723 native context/access/reset assertions, 7,680 independent assembler +comparisons and 1,536 actual ESP32-compiled TLS calls across six persistent banks +(16 exact replays, 52,224 retired instructions). Firmware selects the banks; +this does not establish a scheduler, interrupt-driven task switching or SDK boot. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. diff --git a/docs/engineering/esp32-context-registers.md b/docs/engineering/esp32-context-registers.md new file mode 100644 index 000000000..eb4fe87b2 --- /dev/null +++ b/docs/engineering/esp32-context-registers.md @@ -0,0 +1,106 @@ +# ESP32 CPU task-context registers and compiler TLS + +**Implemented: bounded functional THREADPTR/MISC access and explicitly configured +PRID input in the experimental Xtensa engine. No runnable ESP32 board, SDK boot +or RTOS scheduler is added.** + +## Architectural contract + +The original ESP32 toolchain configuration enables THREADPTR, PRID and four +MISC registers. The Xtensa ISA Reference Manual, RI-2021.8, defines their access: + +| Register | Namespace/address | Access | Mode | +|---|---|---|---| +| THREADPTR | User register 231 | RUR/WUR, full 32 bits | Unprivileged | +| MISC0..3 | Special registers 244..247 | RSR/WSR/XSR, full 32 bits | Privileged | +| PRID | Special register 235 | RSR, full 32 bits | Privileged, read-only | + +THREADPTR's user-register namespace is separate from VECBASE's special-register +namespace, although both use number 231. RUR and WUR encode their A-register +operand in different fields. Only the actual operand participates in lazy +register-window checks. A window transition retries the instruction without +changing THREADPTR or MISC. User-mode MISC/PRID access enters the configured +privilege exception, before changing the destination or special register. +Other user registers, FPU controls and nonexistent MISC4 remain unsupported. + +THREADPTR holds the compiler's per-thread storage base; writing it does not +allocate, initialize or copy memory. MISC registers hold software-defined scratch +state. They are per CPU, with no host task-storage substitution. Interrupt entry +and return do not automatically save or restore these registers; firmware must +manage its own context. Init zeros them for ABI fixtures. Supported CPU reset +preserves their undefined architectural values as a deterministic fixture policy, +not a claim that silicon retains them. + +PRID is a read-only processor identification input. The entire silicon value has +not been established for the requested board. `esk_xtensa_prid_configure` therefore +requires an explicit constant test/SoC input; an unconfigured read is unsupported. +The configured constant survives the supported CPU reset. It is not a sampled +pin waveform, guessed silicon revision or eFuse substitute. The pinned ESP-IDF +ESP32 CPU helper extracts core identity from bit 13; that bit's meaning does not +establish the other bits. Fixture constants `0xa5a50000` and `0xa5a52000` exercise +this extraction and full-width readback; neither is presented as a real chip value. + +## Evidence + +`test_xtensa_context` passes 9,723 assertions covering every operand register, THREADPTR access across +PS.RING/EXCM combinations, all MISC read/write/exchange operations, full-width +fixture PRID reads, read-only rejection, unsupported neighbors, privilege/window +priority, reset policy, NULL configuration and CPU isolation. Unknown instructions +remain failures, rather than fabricated illegal-instruction exceptions. + +`run_context.py` uses independently GNU-assembled instructions and a Python +register oracle: **7,680 comparisons**. It checks all 16 operands, all four MISC +registers and unprivileged THREADPTR behavior. It also hash-pins and checks the +actual original-ESP32 toolchain feature configuration. + +`run_tls_c.py` compiles real C `__thread` variables with Espressif GCC and links +local-exec TLS relocations. Firmware writes THREADPTR/MISC0, calls a non-inlined C +function that updates TLS, reads PRID and records the TLS variable address. Both +cores execute through the native shared RAM bus, interleaved one instruction at +a time. Six persistent TLS banks are initialized from ELF PT_TLS, including +zero-filled `.tbss`. The Xtensa linker adds +`align_up(TCB_SIZE=8, TLS alignment)` to TLS offsets, as documented in the pinned +ESP-IDF FreeRTOS port; the fixture accounts for this without changing target code. + +**1,536 calls, 16 exact replays and 52,224 retired instructions pass.** Checks +include all inactive banks, initial/updated values, zero initialization, guards, +compiler-produced addresses, full-width MISC cookies, core-ID bit extraction, +window return and allocation cleanup. There are no Python memory callbacks. +Host ABI setup starts each function call; this is firmware-selected bank +acceptance, not proof of interrupt-driven preemption, migration, complete context +switching, FreeRTOS boot or cache coherence. Retired instructions are not cycles. + +## Reproduction and provenance + +Use the external Espressif GCC 8.4.0 esp-2021r2-patch5 toolchain already pinned in +[the target investigation](esp32-dual-core.md). Its `core-isa.h` SHA-256 is +`60d20f76c7730b24041b9f55b16e0062f30bf85c7661c7ed215f050fedcdb344`. + +Additional external ESP-IDF v4.4.7 inputs, inspected on 2026-11-05: + +| Source | SHA-256 | Fact used | +|---|---|---| +| [components/hal/esp32/include/hal/cpu_ll.h](https://github.com/espressif/esp-idf/blob/v4.4.7/components/hal/esp32/include/hal/cpu_ll.h) | `0b75eb5517f4ae4a0daf13574ee27aaa33394aa5149e89a4dab89471abaa18e1` | `rsr.prid`, `extui ...,13,1` | +| [components/freertos/port/xtensa/port.c](https://github.com/espressif/esp-idf/blob/v4.4.7/components/freertos/port/xtensa/port.c) | `b81102899f12b67b1228ad3ad9deb8ce5a71fb5155691e225672ccdd146a1052` | TLS linker offset and 8-byte TCB constant | + +These files remain external test inputs; no vendor task scheduler or processor +implementation is copied into the runtime. The generated C/assembly fixture is +original test code. From the repository root, with actual tool paths substituted: + +```sh +python3 tests/vendor/xtensa_integer/run_context.py \ + --core-isa /path/to/xtensa/config/core-isa.h \ + --assembler /path/to/xtensa-esp32-elf-as \ + --objcopy /path/to/xtensa-esp32-elf-objcopy +python3 tests/vendor/xtensa_integer/run_tls_c.py \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld \ + --nm /path/to/xtensa-esp32-elf-nm \ + --port-source /path/to/esp-idf/components/freertos/port/xtensa/port.c \ + --cpu-ll /path/to/esp-idf/components/hal/esp32/include/hal/cpu_ll.h +``` + +The runtime remains independent of these tools and SDK files. Full ISA/FPU/debug, +actual ROM/SDK boot, clocks/peripherals/radio and physical board qualification are +still separate gates. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 964556232..a4ca947c3 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -790,6 +790,17 @@ fixture's checksum/hash and generated ten files accepted by the native inspector (with/without digest and zero to 1024-byte payloads). These synthetic files prove format compatibility, not firmware execution or physical-board equivalence. +## Implemented: CPU task-context registers and compiler TLS + +The [context register contract](esp32-context-registers.md) adds unprivileged +RUR/WUR.THREADPTR, privileged MISC0..3 read/write/exchange and an explicit +read-only PRID fixture input. Undefined THREADPTR/MISC reset values follow the +declared deterministic policy; full silicon PRID remains unresolved. Native +context tests (9,723 assertions), 7,680 independently assembled comparisons and 1,536 actual ESP32 +C TLS calls pass through native RAM (six persistent banks, 16 exact replays, +52,224 retired instructions). No scheduler, complete context switch or real +ROM/SDK boot claim follows. + ## Establish identity before catalog publication The implementation needs a tuple, not a marketing name: diff --git a/tests/unit/test_xtensa_context.c b/tests/unit/test_xtensa_context.c new file mode 100644 index 000000000..aee02fce9 --- /dev/null +++ b/tests/unit/test_xtensa_context.c @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/xtensa/windows.h" +static es_status execute(esk_xtensa_windows *w,uint32_t word,unsigned *event) +{ + unsigned char code[3];size_t used=99;esk_xtensa_fault fault;es_status st; + code[0]=(unsigned char)(word&255u);code[1]=(unsigned char)((word>>8)&255u);code[2]=(unsigned char)((word>>16)&255u); + st=esk_xtensa_window_execute(w,NULL,code,3,&used,event,&fault); + CHECK_EQ(used,st==ES_OK && !*event ? 3u : 0u);return st; +} +static void threadptr_registers_and_modes(void) +{ + unsigned reg,mode,k,event;const uint32_t values[]={0,UINT32_MAX,0x12345679,0x3ffb0000}; + for(reg=0;reg<16;reg++) for(mode=0;mode<8;mode++) for(k=0;k> 13) & 1; + out[2] = got; + return value; +} +''' + + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + for name in ['compiler', 'objcopy', 'linker', 'nm']: + ap.add_argument('--' + name, required=True) + ap.add_argument('--port-source', type=Path, required=True) + ap.add_argument('--cpu-ll', type=Path, required=True) + ap.add_argument('--cc', default='cc'); args = ap.parse_args(); rng = random.Random(0x715) + port = read_header(args.port_source, 'b81102899f12b67b1228ad3ad9deb8ce5a71fb5155691e225672ccdd146a1052') + cpu_ll = read_header(args.cpu_ll, '0b75eb5517f4ae4a0daf13574ee27aaa33394aa5149e89a4dab89471abaa18e1') + assert 'const uint32_t tcb_size = 8;' in port and 'tls_section_alignment' in port + assert 'rsr.prid' in cpu_ll and 'extui %0,%0,13,1' in cpu_ll + with tempfile.TemporaryDirectory(prefix='embsim-tls-c-') as tmp: + out = Path(tmp); src, obj, elf, binary = [out / ('tls.' + ext) for ext in ['c', 'o', 'elf', 'bin']] + src.write_text(SOURCE, encoding='utf-8') + subprocess.run([args.compiler, '-B' + str(Path(args.compiler).parent) + '/', '-O1', '-ffreestanding', + '-fno-builtin', '-ftls-model=local-exec', '-mtext-section-literals', '-c', str(src), '-o', str(obj)], check=True) + subprocess.run([args.linker, '--no-relax', '-Ttext=0x40080000', '--entry=task', str(obj), '-o', str(elf)], check=True) + subprocess.run([args.objcopy, '-O', 'binary', '--only-section=.text', str(elf), str(binary)], check=True) + symbols = {f[2]: int(f[0], 16) for line in subprocess.check_output([args.nm, str(elf)], text=True).splitlines() + if len(f := line.split()) == 3} + # ELF32 PT_TLS describes the initial per-task template, including .tbss. + raw = elf.read_bytes(); assert raw[:7] == b'\x7fELF\x01\x01\x01' + phoff = struct.unpack_from(' Date: Thu, 5 Nov 2026 04:53:55 +0000 Subject: [PATCH 21/57] Execute Xtensa MOVSP and dynamic stack allocation paths --- CHANGELOG.md | 10 +++ core/mcu/xtensa/windows.c | 13 +++- core/mcu/xtensa/windows.h | 2 + docs/STATUS.md | 5 ++ docs/engineering/esp32-context-registers.md | 42 ++++++++++- docs/engineering/esp32-dual-core.md | 9 ++- tests/unit/test_xtensa_alloca.c | 77 +++++++++++++++++++++ tests/vendor/README.md | 8 +++ tests/vendor/xtensa_integer/run_alloca.py | 77 +++++++++++++++++++++ tests/vendor/xtensa_integer/run_tls_c.py | 19 +++-- 10 files changed, 251 insertions(+), 11 deletions(-) create mode 100644 tests/unit/test_xtensa_alloca.c create mode 100644 tests/vendor/xtensa_integer/run_alloca.py diff --git a/CHANGELOG.md b/CHANGELOG.md index bfa497706..ec8f0f69a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental Xtensa dynamic stack allocation + +- Added MOVSP with wrapped caller-window checks and AllocaCause 5 before SP + changes, including ordinary exception entry, retry and loop commit behavior. + Firmware owns base-save-area restoration; no host stack-repair shortcut. +- Added 13,475 native assertions, 32,768 independent assembler comparisons and + 1,536 actual compiled TLS calls with dynamic buffers (16 exact replays, + 253,812 retired instructions). Full vendor alloca-handler/RTOS boot remains + unqualified. + ### Experimental ESP32 thread-local storage execution - Added unprivileged RUR/WUR.THREADPTR and privileged RSR/WSR/XSR.MISC0..3, diff --git a/core/mcu/xtensa/windows.c b/core/mcu/xtensa/windows.c index 5eb4431a2..76b9688a3 100644 --- a/core/mcu/xtensa/windows.c +++ b/core/mcu/xtensa/windows.c @@ -8,7 +8,7 @@ enum execution_kind { XT_BASE, XT_CALL, XT_ENTRY, XT_RETW, XT_WINDOW_RETURN, XT_ROTW, XT_SPECIAL, XT_RSYNC, XT_LOOP, XT_EXCEPTION_MEMORY, XT_SAR_SETUP=11, XT_SAR_SHIFT, XT_FENCE, XT_RSIL, XT_SYSCALL, - XT_EXCEPTION_RETURN, XT_ILLEGAL, XT_RFI, XT_WAITI, XT_ATOMIC, XT_DIVIDE, XT_THREADPTR + XT_EXCEPTION_RETURN, XT_ILLEGAL, XT_RFI, XT_WAITI, XT_ATOMIC, XT_DIVIDE, XT_THREADPTR, XT_MOVSP }; static uint32_t bit(unsigned n) { return UINT32_C(1) << (n & 15u); } @@ -174,7 +174,8 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo if (s>3 || !(next.ps&ESK_XT_PS_WOE) || (next.ps&ESK_XT_PS_EXCM)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); mask=bit(n*4+s)|bit(s); - } else if (word==0x000090 || word==0xf01d) kind=XT_RETW; + } else if ((word&UINT32_C(0xfff00f))==0x001000) { kind=XT_MOVSP;mask=bit(s)|bit(t); } + else if (word==0x000090 || word==0xf01d) kind=XT_RETW; else if (word==0x003400 || word==0x003500) kind=XT_WINDOW_RETURN; else if ((word&UINT32_C(0xffff0f))==0x408000) kind=XT_ROTW; else if ((word&UINT32_C(0xff000f))==0x030000 || (word&UINT32_C(0xff000f))==0x130000 || (word&UINT32_C(0xff000f))==0x610000) { @@ -219,7 +220,13 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo } e=overflow(&next,mask); if (e) { *w=next; *event=e; return ES_OK; } - if (kind==XT_CALL) { + if (kind==XT_MOVSP) { + if (!(next.start&(bit(next.base-1u)|bit(next.base-2u)|bit(next.base-3u)))) { + if(!next.exceptions_enabled) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + st=esk_xtensa_raise_exception(&next,5,0,0,event);if(st==ES_OK) *w=next;return st; + } + next.cpu.a[t]=next.cpu.a[s];next.cpu.pc=pc+3; + } else if (kind==XT_CALL) { next.ps=(next.ps&~UINT32_C(0x30000)) | (n<<16); next.cpu.a[n*4]=(n<<30)|((pc+3)&UINT32_C(0x3fffffff)); next.cpu.pc=target; flow=0; } else if (kind==XT_ENTRY) { diff --git a/core/mcu/xtensa/windows.h b/core/mcu/xtensa/windows.h index 47d044ed6..f079f1794 100644 --- a/core/mcu/xtensa/windows.h +++ b/core/mcu/xtensa/windows.h @@ -12,6 +12,8 @@ * instructions/exception transitions. Init configures an ABI test state, not * silicon power-on defaults. Legacy vectors are caller-supplied; explicit * reset/relocation profiles use pinned original-ESP32 vector configuration. + * MOVSP checks the preceding three windows and raises configured AllocaCause + * without changing SP; firmware owns base-save-area relocation and retry. * Selected special registers, SAR and opt-in XEA2-with-DEPC exception entry * are implemented. CPU interrupt/timer delivery is opt-in; the SoC interrupt * matrix, automatic bus/MMU fault dispatch, complete ISA, MMU/caches and diff --git a/docs/STATUS.md b/docs/STATUS.md index b1f084ecc..7383bcc2c 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -108,6 +108,11 @@ Evidence: 9,723 native context/access/reset assertions, 7,680 independent assemb comparisons and 1,536 actual ESP32-compiled TLS calls across six persistent banks (16 exact replays, 52,224 retired instructions). Firmware selects the banks; this does not establish a scheduler, interrupt-driven task switching or SDK boot. +MOVSP now implements caller-window checks and configured AllocaCause 5 before +changing SP. Evidence: 13,475 native assertions, 32,768 independent assembler +comparisons and another 1,536 actual TLS C calls with dynamic stack buffers +(16 replays, 253,812 retired instructions). Vendor alloca-handler restoration, +preemptive scheduling and RTOS boot remain unqualified. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. diff --git a/docs/engineering/esp32-context-registers.md b/docs/engineering/esp32-context-registers.md index eb4fe87b2..22538f3bd 100644 --- a/docs/engineering/esp32-context-registers.md +++ b/docs/engineering/esp32-context-registers.md @@ -1,4 +1,4 @@ -# ESP32 CPU task-context registers and compiler TLS +# ESP32 CPU task-context registers, compiler TLS and dynamic stack frames **Implemented: bounded functional THREADPTR/MISC access and explicitly configured PRID input in the experimental Xtensa engine. No runnable ESP32 board, SDK boot @@ -70,6 +70,37 @@ Host ABI setup starts each function call; this is firmware-selected bank acceptance, not proof of interrupt-driven preemption, migration, complete context switching, FreeRTOS boot or cache coherence. Retired instructions are not cycles. +## Dynamic stack frames and MOVSP + +MOVSP is the Xtensa ABI's atomic caller-window check and register move. The +RI-2021.8 ISA operation checks WINDOWSTART at WINDOWBASE-1, -2 and -3, wrapping +around the 16-window file. If any of those windows is present, the source moves +to the destination and execution advances normally. Otherwise configured +AllocaCause 5 enters the ordinary kernel/user/double exception path before any +stack-pointer change. Register-window overflow checks precede AllocaCause. +Without the exception profile, the missing-caller case fails unchanged with +PC/opcode diagnostics. A successful MOVSP participates in hardware loop commit; +an exception does not consume an iteration. + +Firmware remains responsible for restoring the caller window/base-save area and +retrying. The engine does not silently adjust a stack frame or fabricate a caller +window. This work does not qualify ESP-IDF's complete alloca exception handler. + +`test_xtensa_alloca` passes **13,475 assertions** for all window bases, all eight +caller-presence combinations, exception modes, all source/destination operands, +overflow priority, explicit handler retry, disabled-profile diagnostics and loop +commit. `run_alloca.py` passes **32,768 independently GNU-assembled comparisons** +with randomized register values, unrelated-window decoys, wrapped bases and +kernel/user/double entry. + +Adding `--dynamic-stack` to `run_tls_c.py` compiles a runtime-sized volatile +`__builtin_alloca` buffer inside the TLS update function. Both cores execute the +actual compiler's MOVSP and return to their original stack pointer. All six TLS +banks and guards are checked as before: **1,536 calls, 1,536 MOVSP executions, +16 exact replays and 253,812 retired instructions**. These compiler runs keep +the caller window resident; missing-caller exception/retry is covered separately +by native and assembler tests. This is not preemptive RTOS or timing evidence. + ## Reproduction and provenance Use the external Espressif GCC 8.4.0 esp-2021r2-patch5 toolchain already pinned in @@ -101,6 +132,15 @@ python3 tests/vendor/xtensa_integer/run_tls_c.py \ --cpu-ll /path/to/esp-idf/components/hal/esp32/include/hal/cpu_ll.h ``` +Run the dynamic-stack variant by adding `--dynamic-stack` to the TLS command. +The independent MOVSP referee uses: + +```sh +python3 tests/vendor/xtensa_integer/run_alloca.py \ + --assembler /path/to/xtensa-esp32-elf-as \ + --objcopy /path/to/xtensa-esp32-elf-objcopy +``` + The runtime remains independent of these tools and SDK files. Full ISA/FPU/debug, actual ROM/SDK boot, clocks/peripherals/radio and physical board qualification are still separate gates. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index a4ca947c3..14fb8a67a 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -798,8 +798,13 @@ read-only PRID fixture input. Undefined THREADPTR/MISC reset values follow the declared deterministic policy; full silicon PRID remains unresolved. Native context tests (9,723 assertions), 7,680 independently assembled comparisons and 1,536 actual ESP32 C TLS calls pass through native RAM (six persistent banks, 16 exact replays, -52,224 retired instructions). No scheduler, complete context switch or real -ROM/SDK boot claim follows. +52,224 retired instructions). MOVSP now checks the preceding three windows and +raises AllocaCause 5 before a stack move when restoration is needed. Another +13,475 native assertions and 32,768 assembled comparisons pass; the TLS runner's +`--dynamic-stack` variant passes 1,536 compiled dynamic-buffer calls and +253,812 instructions. Firmware owns base-save restoration and retry. No full +vendor alloca handler, scheduler, complete context switch or real ROM/SDK boot +claim follows. ## Establish identity before catalog publication diff --git a/tests/unit/test_xtensa_alloca.c b/tests/unit/test_xtensa_alloca.c new file mode 100644 index 000000000..f60d1bb4c --- /dev/null +++ b/tests/unit/test_xtensa_alloca.c @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/xtensa/windows.h" +static es_status execute(esk_xtensa_windows *w,uint32_t word,unsigned *event) +{ + unsigned char code[3];size_t used=99;esk_xtensa_fault fault;es_status st; + code[0]=(unsigned char)(word&255u);code[1]=(unsigned char)((word>>8)&255u);code[2]=(unsigned char)((word>>16)&255u); + st=esk_xtensa_window_execute(w,NULL,code,3,&used,event,&fault); + CHECK_EQ(used,st==ES_OK && !*event ? 3u : 0u); + if(st!=ES_OK) { CHECK_EQ(fault.kind,ESK_XT_UNSUPPORTED);CHECK_EQ(fault.pc,w->cpu.pc);CHECK_EQ(fault.opcode,word); } + return st; +} +static uint32_t window_bit(unsigned base,unsigned distance) { return UINT32_C(1)<<((base-distance)&15u); } +static void caller_windows_and_exception_modes(void) +{ + unsigned base,pattern,mode,event,i;const uint32_t vectors[3]={0x40081000,0x40081400,0x40081800}; + for(base=0;base<16;base++) for(pattern=0;pattern<8;pattern++) for(mode=0;mode<8;mode++) { + esk_xtensa_windows w;uint32_t old_ps=ESK_XT_PS_WOE|((mode&1u) ? ESK_XT_PS_EXCM : 0u)|((mode&2u) ? 32u : 0u)|((mode&4u) ? 64u : 0u); + esk_xtensa_windows_init(&w,0x40080000,NULL);esk_xtensa_exception_configure(&w,vectors); + w.base=base;w.start=window_bit(base,0);w.ps=old_ps;w.cpu.a[1]=0x3ffd0000;w.cpu.a[2]=0x3ffc0000; + for(i=0;i<3;i++) if(pattern&(1u< Date: Thu, 5 Nov 2026 11:00:12 +0000 Subject: [PATCH 22/57] Implement bounded ESP32 SPI1 flash reads and completion routing --- CHANGELOG.md | 13 ++ core/mcu/esp32/fabric.c | 10 ++ core/mcu/esp32/fabric.h | 9 +- core/mcu/esp32/flashmap.c | 18 +- core/mcu/esp32/flashmap.h | 7 +- core/mcu/esp32/flashspi.c | 83 +++++++++ core/mcu/esp32/flashspi.h | 21 +++ core/mcu/esp32/machine.c | 18 +- core/mcu/esp32/machine.h | 3 + docs/STATUS.md | 9 + docs/engineering/esp32-dual-core.md | 12 ++ docs/engineering/esp32-spi1-reads.md | 136 +++++++++++++++ tests/unit/test_esp32_flashspi.c | 151 ++++++++++++++++ tests/vendor/README.md | 9 + tests/vendor/xtensa_integer/run_controls.py | 2 +- tests/vendor/xtensa_integer/run_flash_c.py | 2 +- tests/vendor/xtensa_integer/run_flashmap.py | 2 +- tests/vendor/xtensa_integer/run_flashspi.py | 82 +++++++++ tests/vendor/xtensa_integer/run_ipi_c.py | 2 +- tests/vendor/xtensa_integer/run_machine_c.py | 2 +- tests/vendor/xtensa_integer/run_reset_c.py | 2 +- tests/vendor/xtensa_integer/run_spi_c.py | 172 +++++++++++++++++++ tests/vendor/xtensa_integer/run_tls_c.py | 2 +- 23 files changed, 754 insertions(+), 13 deletions(-) create mode 100644 core/mcu/esp32/flashspi.c create mode 100644 core/mcu/esp32/flashspi.h create mode 100644 docs/engineering/esp32-spi1-reads.md create mode 100644 tests/unit/test_esp32_flashspi.c create mode 100644 tests/vendor/xtensa_integer/run_flashspi.py create mode 100644 tests/vendor/xtensa_integer/run_spi_c.py diff --git a/CHANGELOG.md b/CHANGELOG.md index ec8f0f69a..cf7b6b993 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental ESP32 SPI1 flash reads + +- Added an opt-in USER 03h single-line read path to the installed immutable + flash backing, with real register addresses, 24-bit address alignment and + high/low data buffers. Both caches must be disabled for transfers. +- Added completion/counter state and SPI1 matrix-source ownership, synchronized + after instruction commit. Unsupported commands/modes and out-of-extent reads + fail unchanged. Programming, erase, DMA, timing and real ROM/SDK boot remain + unimplemented. +- Added 37,466 native assertions, 27 parsed vendor register defaults/384 reference + comparisons and actual ESP32 C with seven unchanged SDK helper bodies: + 128 reads, 32 SPI-loaded RAM workers, 32 replays and 91,568 retired instructions. + ### Experimental Xtensa dynamic stack allocation - Added MOVSP with wrapped caller-window checks and AllocaCause 5 before SP diff --git a/core/mcu/esp32/fabric.c b/core/mcu/esp32/fabric.c index 3f357b2a7..962a0f02b 100644 --- a/core/mcu/esp32/fabric.c +++ b/core/mcu/esp32/fabric.c @@ -124,6 +124,16 @@ es_status esk_esp32_fabric_flash(esk_esp32_fabric *f,const unsigned char *data,s if(st!=ES_OK) { esk_esp32_flashmap_destroy(created);return st; } f->flash=created;return ES_OK; } +es_status esk_esp32_fabric_spi1_configure(esk_esp32_fabric *f,int enabled) +{ + if(!f) return ES_EINVAL; + return f->flash ? esk_esp32_flashmap_spi1_configure(f->flash,enabled) : ES_EUNSUPPORTED; +} +es_status esk_esp32_fabric_spi1_irq(const esk_esp32_fabric *f,int *asserted) +{ + if(!f || !asserted) return ES_EINVAL; + return f->flash ? esk_esp32_flashmap_spi1_irq(f->flash,asserted) : ES_EUNSUPPORTED; +} es_status esk_esp32_fabric_remap(esk_esp32_fabric *f,unsigned cpu,int enabled) { if(!f || cpu>1) return ES_EINVAL; diff --git a/core/mcu/esp32/fabric.h b/core/mcu/esp32/fabric.h index 93888c3fb..87d3aa6e6 100644 --- a/core/mcu/esp32/fabric.h +++ b/core/mcu/esp32/fabric.h @@ -8,7 +8,8 @@ typedef struct esk_esp32_fabric esk_esp32_fabric; /* Experimental static internal-memory profile, not the full SoC bus. SRAM0 * non-cache 128K, SRAM1 128K (reversed instruction words), SRAM2 200K, RTC * fast/slow 8K each. RTC fast is PRO-only. Optional read-only flash mapping - * is described below. No full cache/MMU/MPU, peripheral/DMA, RTC power/retention + * and an opt-in SPI1 read path are described below. No full cache/MMU/MPU, + * other peripherals/DMA, RTC power/retention * or physical reset qualification. RAM starts zero only * for deterministic setup. Serialized single-thread calls only; no re-entry. */ es_status esk_esp32_fabric_create(esk_alloc *a,esk_esp32_fabric **out); @@ -23,6 +24,12 @@ es_status esk_esp32_fabric_rom(esk_esp32_fabric *f,unsigned which,const unsigned * Replacement requires both caches disabled and preserves mappings; allocation * failure leaves the old image/state intact. Host calls only between turns. */ es_status esk_esp32_fabric_flash(esk_esp32_fabric *f,const unsigned char *data,size_t len); +/* Opt-in SPI1 USER 03h read path to the installed immutable backing. Both + * caches must be disabled during transfers. No chip ID/program/erase/timing. + * Enabling/disabling preserves SPI state; CPU-only reset also preserves it. */ +es_status esk_esp32_fabric_spi1_configure(esk_esp32_fabric *f,int enabled); +/* ES_EUNSUPPORTED means disabled/unowned; output is unchanged on failure. */ +es_status esk_esp32_fabric_spi1_irq(const esk_esp32_fabric *f,int *asserted); /* Host-controlled boot-remap bit, not a DPORT register model. */ es_status esk_esp32_fabric_remap(esk_esp32_fabric *f,unsigned cpu,int enabled); /* All-or-nothing RAM image write through the selected core's address view. */ diff --git a/core/mcu/esp32/flashmap.c b/core/mcu/esp32/flashmap.c index 50ca8fc95..21470cb16 100644 --- a/core/mcu/esp32/flashmap.c +++ b/core/mcu/esp32/flashmap.c @@ -3,10 +3,12 @@ /* Facts: ESP32 TRM v5.9 ch.3/4/12, ESP-IDF v4.4.7 soc/mmu.h and dport_reg.h. * Original functional translation implementation, no vendor controller code. */ #include "mcu/esp32/flashmap.h" +#include "mcu/esp32/flashspi.h" #include struct esk_esp32_flashmap { esk_alloc *a;unsigned char *image;size_t size; uint32_t table[2][256],ctrl[2],ctrl1[2],dirty[2]; + esk_esp32_flashspi spi1;int spi1_enabled; }; es_status esk_esp32_flashmap_create(esk_alloc *a,esk_esp32_flashmap **out) { @@ -14,7 +16,7 @@ es_status esk_esp32_flashmap_create(esk_alloc *a,esk_esp32_flashmap **out) if(!out) return ES_EINVAL; *out=NULL;if(!a) return ES_EINVAL; f=(esk_esp32_flashmap *)esk_calloc(a,1,sizeof *f);if(!f) return ES_ENOMEM; - f->a=a; + f->a=a;esk_esp32_flashspi_init(&f->spi1); for(cpu=0;cpu<2;cpu++) { f->ctrl[cpu]=0x10;f->ctrl1[cpu]=0x8ff;f->dirty[cpu]=1; for(i=0;i<256;i++) f->table[cpu][i]=0x100; @@ -36,6 +38,18 @@ es_status esk_esp32_flashmap_install(esk_esp32_flashmap *f,const unsigned char * memcpy(copy,data,len);if(f->image) esk_free(f->a,f->image,f->size); f->image=copy;f->size=len;f->dirty[0]=f->dirty[1]=1;return ES_OK; } +es_status esk_esp32_flashmap_spi1_configure(esk_esp32_flashmap *f,int enabled) +{ + if(!f) return ES_EINVAL; + if(enabled && !f->image) return ES_EUNSUPPORTED; + f->spi1_enabled=enabled!=0;return ES_OK; +} +es_status esk_esp32_flashmap_spi1_irq(const esk_esp32_flashmap *f,int *asserted) +{ + if(!f || !asserted) return ES_EINVAL; + if(!f->spi1_enabled) return ES_EUNSUPPORTED; + *asserted=esk_esp32_flashspi_irq(&f->spi1);return ES_OK; +} /* kind=0 MMU entry, 1 control, 2 masks. Only PID0/1 entry ranges exist here. */ static es_status decode(uint32_t address,unsigned bytes,unsigned *cpu,unsigned *kind,unsigned *index) { @@ -51,6 +65,7 @@ es_status esk_esp32_flashmap_reg_read(const esk_esp32_flashmap *f,uint32_t addre { unsigned cpu,kind,index;es_status st; if(!f || !value) return ES_EINVAL; + if(f->spi1_enabled && address>=0x3ff42000 && address<0x3ff42400) return esk_esp32_flashspi_read(&f->spi1,address,bytes,value); st=decode(address,bytes,&cpu,&kind,&index);if(st!=ES_OK) return st; *value=kind==0 ? f->table[cpu][index] : kind==1 ? f->ctrl[cpu] : f->ctrl1[cpu];return ES_OK; } @@ -58,6 +73,7 @@ es_status esk_esp32_flashmap_reg_write(esk_esp32_flashmap *f,uint32_t address,un { unsigned cpu,kind,index;uint32_t next;es_status st; if(!f) return ES_EINVAL; + if(f->spi1_enabled && address>=0x3ff42000 && address<0x3ff42400) return esk_esp32_flashspi_write(&f->spi1,f->image,f->size,((f->ctrl[0]|f->ctrl[1])&8u)==0,address,bytes,value); st=decode(address,bytes,&cpu,&kind,&index);if(st!=ES_OK) return st; if(kind==0) { if((value&~UINT32_C(0x1ff)) || (f->ctrl[cpu]&8u)) return ES_EUNSUPPORTED; diff --git a/core/mcu/esp32/flashmap.h b/core/mcu/esp32/flashmap.h index 02f62dbcc..dd4af9421 100644 --- a/core/mcu/esp32/flashmap.h +++ b/core/mcu/esp32/flashmap.h @@ -10,7 +10,9 @@ * To keep immutable-image reads qualified without cache-coherence guesses, * table edits require the destination cache disabled and a fresh flush before * enabling. Flash replacement requires both caches disabled. Unsupported modes - * fail unchanged. Initial invalid entries are fixture policy, not silicon reset. + * fail unchanged. Optional SPI1 USER reads use the same immutable backing + * under the separate flashspi.h contract. Initial invalid entries are fixture + * policy, not silicon reset. * No concurrent callers or re-entry. */ typedef struct esk_esp32_flashmap esk_esp32_flashmap; es_status esk_esp32_flashmap_create(esk_alloc *a,esk_esp32_flashmap **out); @@ -18,6 +20,9 @@ void esk_esp32_flashmap_destroy(esk_esp32_flashmap *f); /* Complete provided backing extent: 1..256 whole 64K pages, copied privately. * No chip identity/capacity is inferred and no missing bytes are fabricated. */ es_status esk_esp32_flashmap_install(esk_esp32_flashmap *f,const unsigned char *data,size_t len); +/* Explicit immutable SPI1 USER 03h read attachment; see flashspi.h. */ +es_status esk_esp32_flashmap_spi1_configure(esk_esp32_flashmap *f,int enabled); +es_status esk_esp32_flashmap_spi1_irq(const esk_esp32_flashmap *f,int *asserted); es_status esk_esp32_flashmap_reg_read(const esk_esp32_flashmap *f,uint32_t address,unsigned bytes,uint32_t *value); es_status esk_esp32_flashmap_reg_write(esk_esp32_flashmap *f,uint32_t address,unsigned bytes,uint32_t value); es_status esk_esp32_flashmap_read(const esk_esp32_flashmap *f,unsigned cpu,uint32_t address,unsigned bytes,int fetch,uint32_t *value); diff --git a/core/mcu/esp32/flashspi.c b/core/mcu/esp32/flashspi.c new file mode 100644 index 000000000..d48fe9b43 --- /dev/null +++ b/core/mcu/esp32/flashspi.c @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* Register facts: ESP32 TRM v5.9 ch.20 and ESP-IDF v4.4.7 spi_reg.h. + * SPI USER address alignment: that SDK's spi_flash_ll_set_usr_address. */ +#include "mcu/esp32/flashspi.h" +#include +void esk_esp32_flashspi_init(esk_esp32_flashspi *s) +{ + if(!s) return; + memset(s,0,sizeof *s);s->ctrl=0x20a400;s->user=0x80000040;s->user1=0x5c000007;s->user2=0x70000000;s->slave=0x200; +} +static es_status decode(uint32_t address,unsigned bytes,unsigned *offset) +{ + if(address<0x3ff42000 || address>=0x3ff42400) return ES_EUNSUPPORTED; + if(bytes!=4 || (address&3u)) return ES_EINVAL; + *offset=address-0x3ff42000;return ES_OK; +} +es_status esk_esp32_flashspi_read(const esk_esp32_flashspi *s,uint32_t address,unsigned bytes,uint32_t *value) +{ + unsigned offset;es_status st; + if(!s || !value) return ES_EINVAL; + st=decode(address,bytes,&offset);if(st!=ES_OK) return st; + if(offset>=0x80 && offset<0xc0) { *value=s->buffer[(offset-0x80)/4];return ES_OK; } + switch(offset) { + case 0:*value=0;break;case 4:*value=s->addr;break;case 8:*value=s->ctrl;break; + case 0x18:*value=0x80003043;break;case 0x1c:*value=s->user;break;case 0x20:*value=s->user1;break; + case 0x24:*value=s->user2;break;case 0x28:*value=s->mosi;break;case 0x2c:*value=s->miso;break; + case 0x34:*value=6;break;case 0x38:*value=s->slave;break; + default:return ES_EUNSUPPORTED; + } + return ES_OK; +} +static es_status transfer(esk_esp32_flashspi *s,const unsigned char *image,size_t size,int caches_disabled) +{ + uint32_t address=s->addr>>8,bits=s->miso+1u;unsigned first=(s->user&0x1000000u) ? 32u : 0u,i; + /* No state changes until every command, mode and extent check passes. */ + if(!caches_disabled || !image || (s->ctrl&0x2000u) || + (s->user&~UINT32_C(0x1000000))!=0xd0000040u || s->user2!=0x70000003u || + bits%8u || bits>512u-first*8u || (size_t)address>size || bits/8u>size-(size_t)address) + return ES_EUNSUPPORTED; + for(i=0;ibuffer[byte/4u]=(s->buffer[byte/4u]&~(UINT32_C(255)<slave=(s->slave&~UINT32_C(0x7800000))|((((s->slave>>23)+1u)&15u)<<23)|16u; + return ES_OK; +} +es_status esk_esp32_flashspi_write(esk_esp32_flashspi *s,const unsigned char *image,size_t size,int caches_disabled,uint32_t address,unsigned bytes,uint32_t value) +{ + unsigned offset;es_status st; + if(!s) return ES_EINVAL; + st=decode(address,bytes,&offset);if(st!=ES_OK) return st; + if(offset>=0x80 && offset<0xc0) { s->buffer[(offset-0x80)/4]=value;return ES_OK; } + switch(offset) { + case 0: + if(value&UINT32_C(0xfffb0000)) return ES_EUNSUPPORTED; + return (value&0x40000u) ? transfer(s,image,size,caches_disabled) : ES_OK; + case 4:s->addr=value;break; + case 8: + if((value&UINT32_C(0x7f0dc00))!=0x208400u) return ES_EUNSUPPORTED; + s->ctrl=0x208400u|(value&0x2000u);break; + case 0x18:if(value!=0x80003043u) return ES_EUNSUPPORTED;break; + case 0x1c: + /* Preserve unqualified timing/slave/byte-order fields at reset values. */ + if((value&UINT32_C(0x0efffcf1))!=0x40u) return ES_EUNSUPPORTED; + s->user=(value&UINT32_C(0xf1000000))|0x40u;break; + case 0x20: + /* SDK and register comments disagree on address-length writability. + * Only unchanged 24-bit configuration is qualified in this profile. */ + if((value>>26)!=23u) return ES_EUNSUPPORTED; + s->user1=0x5c000000u|(value&255u);break; + case 0x24:s->user2=value&UINT32_C(0xf000ffff);break; + case 0x28:s->mosi=value&UINT32_C(0xffffff);break; + case 0x2c:s->miso=value&UINT32_C(0xffffff);break; + case 0x34:if((value&UINT32_C(0x600039e7))!=6u) return ES_EUNSUPPORTED;break; + case 0x38: + if(value&UINT32_C(0xf8000def)) return ES_EUNSUPPORTED; + s->slave=(s->slave&UINT32_C(0x7800000))|(value&0x210u);break; + default:return ES_EUNSUPPORTED; + } + return ES_OK; +} +int esk_esp32_flashspi_irq(const esk_esp32_flashspi *s) { return s && (s->slave&0x210u)==0x210u; } diff --git a/core/mcu/esp32/flashspi.h b/core/mcu/esp32/flashspi.h new file mode 100644 index 000000000..f9198af69 --- /dev/null +++ b/core/mcu/esp32/flashspi.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_ESP32_FLASHSPI_H +#define ESK_ESP32_FLASHSPI_H +#include "mcu/xtensa/execute.h" +/* Opt-in SPI1 single-line USER 03h read profile, CS0, 24-bit address, + * byte-sized data in the W0..15 buffer, both caches disabled. The attachment + * is explicitly provided immutable flash storage, not an identified chip. + * Functional synchronous completion, no pin waveforms, arbiter/clock timing, + * DMA, dedicated commands, fast/dual/quad, program/erase/status/ID/security, + * global peripheral reset or power model. Unsupported modes fail unchanged. + * Only clocked receive bytes are qualified; other buffer bytes retain fixture + * values, not a silicon retention guarantee. Serialized calls only. */ +typedef struct esk_esp32_flashspi { + uint32_t addr,ctrl,user,user1,user2,mosi,miso,slave,buffer[16]; +} esk_esp32_flashspi; +void esk_esp32_flashspi_init(esk_esp32_flashspi *s); +es_status esk_esp32_flashspi_read(const esk_esp32_flashspi *s,uint32_t address,unsigned bytes,uint32_t *value); +es_status esk_esp32_flashspi_write(esk_esp32_flashspi *s,const unsigned char *image,size_t size,int caches_disabled,uint32_t address,unsigned bytes,uint32_t value); +int esk_esp32_flashspi_irq(const esk_esp32_flashspi *s); +#endif diff --git a/core/mcu/esp32/machine.c b/core/mcu/esp32/machine.c index d4e9e49a2..1178b2a14 100644 --- a/core/mcu/esp32/machine.c +++ b/core/mcu/esp32/machine.c @@ -7,6 +7,7 @@ struct esk_esp32_machine { esk_alloc *a;esk_esp32_fabric *fabric; esk_xtensa_windows cpu[2];uint64_t retired[2];unsigned running[2],executing; uint32_t app_clock,app_stall,app_boot,app_reset,app_reset_enabled,app_reset_vector,app_reset_pending; + unsigned spi1_owned; esk_esp32_matrix matrix;struct machine_view view[2]; }; /* Never mutate an executing CPU from a bus callback. Synchronize the @@ -15,7 +16,12 @@ struct esk_esp32_machine { * across separate retired writes are latched even while the peer is paused. */ static void sync_inputs(esk_esp32_machine *m) { - unsigned cpu,input;const uint32_t internal=UINT32_C(0x200188c0); + unsigned cpu,input;int asserted=0;const uint32_t internal=UINT32_C(0x200188c0); + if(esk_esp32_fabric_spi1_irq(m->fabric,&asserted)==ES_OK) { + (void)esk_esp32_matrix_source(&m->matrix,29,asserted);m->spi1_owned=1; + } else if(m->spi1_owned) { + (void)esk_esp32_matrix_source(&m->matrix,29,0);m->spi1_owned=0; + } for(cpu=0;cpu<2;cpu++) if(m->cpu[cpu].irq.enabled && !(cpu==1 && m->app_reset_enabled && m->app_reset)) { uint32_t levels=esk_esp32_matrix_levels(&m->matrix,cpu); for(input=0;input<32;input++) { @@ -74,7 +80,9 @@ static es_status bus_write(void *ctx,uint32_t address,unsigned bytes,uint32_t va if(st==ES_OK && !v->m->executing) sync_inputs(v->m); return st; } - ram=esk_esp32_fabric_bus(v->m->fabric,v->cpu);return ram.write(ram.ctx,address,bytes,value); + ram=esk_esp32_fabric_bus(v->m->fabric,v->cpu);st=ram.write(ram.ctx,address,bytes,value); + if(st==ES_OK && !v->m->executing) sync_inputs(v->m); + return st; } static es_status bus_fetch(void *ctx,uint32_t address,unsigned bytes,uint32_t *value) { @@ -94,7 +102,11 @@ esk_xtensa_memory esk_esp32_machine_bus(esk_esp32_machine *m,unsigned cpu) } es_status esk_esp32_machine_source(esk_esp32_machine *m,unsigned source,int asserted) { - es_status st;if(!m || m->executing) return ES_EINVAL; + es_status st;int asserted_now=0;if(!m || m->executing) return ES_EINVAL; + if(source==29) { + if(esk_esp32_fabric_spi1_irq(m->fabric,&asserted_now)==ES_OK) return ES_EUNSUPPORTED; + if(m->spi1_owned) sync_inputs(m); + } st=esk_esp32_matrix_source(&m->matrix,source,asserted);if(st==ES_OK) sync_inputs(m);return st; } es_status esk_esp32_machine_gpio_irq(esk_esp32_machine *m,unsigned cpu,int nmi,int asserted) diff --git a/core/mcu/esp32/machine.h b/core/mcu/esp32/machine.h index 01fccd3e7..faf23904a 100644 --- a/core/mcu/esp32/machine.h +++ b/core/mcu/esp32/machine.h @@ -26,6 +26,9 @@ esk_esp32_fabric *esk_esp32_machine_fabric(esk_esp32_machine *m); * not irq_line on CPU peripheral input numbers. Internal timer/software and * profiling inputs remain CPU-local. No API is re-entrant or thread-safe. */ esk_xtensa_memory esk_esp32_machine_bus(esk_esp32_machine *m,unsigned cpu); +/* The opt-in SPI1 read profile owns matrix source 29 while enabled. Its + * completion/enable changes route after commit (immediately for host bus writes). + * External source injection into that owned slot fails explicitly. */ es_status esk_esp32_machine_source(esk_esp32_machine *m,unsigned source,int asserted); es_status esk_esp32_machine_gpio_irq(esk_esp32_machine *m,unsigned cpu,int nmi,int asserted); es_status esk_esp32_machine_nmi_mask(esk_esp32_machine *m,unsigned cpu,int asserted); diff --git a/docs/STATUS.md b/docs/STATUS.md index 7383bcc2c..bddae1250 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -113,6 +113,15 @@ changing SP. Evidence: 13,475 native assertions, 32,768 independent assembler comparisons and another 1,536 actual TLS C calls with dynamic stack buffers (16 replays, 253,812 retired instructions). Vendor alloca-handler restoration, preemptive scheduling and RTOS boot remain unqualified. +An opt-in [SPI1 read profile](engineering/esp32-spi1-reads.md) now executes USER +03h single-line reads against installed immutable flash backing, with receive +buffers, completion/counter state and source-29 interrupt routing. Transfers +require both caches disabled. Evidence: 37,466 native assertions, 27 parsed register defaults/384 +reference comparisons and 128 actual ESP32 C reads pass; 32 of these also load +and execute a generated RAM worker (32 exact replays, 91,568 retired instructions). +Seven unchanged SDK helper bodies are tested, not the full SDK initialization or +boot stack. Flash program/erase, DMA, SPI timing, GPIO/pin mux and actual ROM/SDK +boot remain unsupported. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 14fb8a67a..3d17e3e85 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -806,6 +806,18 @@ raises AllocaCause 5 before a stack move when restoration is needed. Another vendor alloca handler, scheduler, complete context switch or real ROM/SDK boot claim follows. +## Implemented: bounded SPI1 immutable-flash reads + +The [SPI1 read contract](esp32-spi1-reads.md) adds an opt-in USER 03h single-line +read path to the installed backing. Both caches must be disabled. Data-buffer +packing, real address alignment, completion/counter state and matrix source 29 +are functional; unsupported modes/commands and missing extents fail unchanged. +Evidence includes 37,466 native assertions, 27 parsed register defaults/384 reference cases +and 128 actual ESP32 C reads using seven unchanged SDK helper bodies. Thirty-two +runs load and call a generated RAM worker; 32 exact replays retire 91,568 +instructions. Physical flash identity/program/erase, SPI timing, DMA, pin mux, +full SDK initialization and actual ROM/SDK/board boot remain unqualified. + ## Establish identity before catalog publication The implementation needs a tuple, not a marketing name: diff --git a/docs/engineering/esp32-spi1-reads.md b/docs/engineering/esp32-spi1-reads.md new file mode 100644 index 000000000..a2490aac3 --- /dev/null +++ b/docs/engineering/esp32-spi1-reads.md @@ -0,0 +1,136 @@ +# Original ESP32 SPI1 immutable-flash read profile + +**Implemented: opt-in single-line SPI1 USER 03h reads into the hardware data +buffer, with functional completion status and matrix routing. No runnable board, +actual ROM/SDK boot, flash programming or physical SPI timing is added.** + +## Real operation and supported configuration + +ESP32 has four SPI controllers. SPI0 services mapped external-memory accesses; +SPI1 shares its external signal bus through an arbiter. SPI1 firmware can instead +configure a command, address and receive phase, start USER, poll completion and +copy the receive buffer. This path is needed before mapped flash is available. + +The model's attachment is the same immutable, privately copied backing installed +by `esk_esp32_fabric_flash`. No JEDEC ID, chip capacity, erased extent or PCB flash +part is inferred. `esk_esp32_fabric_spi1_configure` explicitly enables/disables +this attachment; before enabling, SPI1 MMIO remains unsupported. Disabling and +re-enabling preserve its state. The standalone machine has no configured pin mux, +flash wiring, peripheral clocks or global reset domains; their correctness is +not implied by opting into this functional fixture. + +Qualified transfers require both CPU caches disabled, single-line slow-read +configuration, CS0 only, an eight-bit 03h command, a 24-bit address, no dummy or +MOSI phase, default clock/timing fields and byte-sized receive data. Requests +receive 1..64 bytes through W0..15, or 1..32 bytes through W8..15 with +MISO_HIGHPART. USER addresses are left-aligned: the transmitted 24-bit address is +`ADDR >> 8`, independent of the low padding byte. SDK +`spi_flash_ll_set_usr_address` supplies ones in that padding byte. + +Every received byte must fall within the supplied backing. Missing/out-of-range +bytes fail without wrapping, padding or invented data. Both caches must be +disabled because SPI0/1 arbitration is unqualified. The model rejects fast/dual/ +quad/three-wire/full-duplex, bit/byte reordering, held CS, DMA, dedicated hardware +commands, identification/status/program/erase/power/security operations and other +unqualified modes. An unsupported transaction changes neither the receive buffer +nor completion/counter state. A command is not reported successful merely +because its start bit was written. + +## Register and completion contract + +Aligned 32-bit data access only; MMIO fetch and CAS fail. Supported registers: + +| Register offset from SPI1 0x3ff42000 | Behavior | +|---|---| +| 0x00 CMD | USER bit 18 starts the qualified operation; completion clears it | +| 0x04 ADDR | Full-width storage, high 24 bits used by this USER profile | +| 0x08 CTRL | Slow/fast selection storage with other defined fields restricted to reset values; only slow read transfers qualify | +| 0x18 CLOCK | Documented reset value; other writes unsupported | +| 0x1c USER | Supported phase/high-buffer configuration; other defined fields restricted to reset values | +| 0x20 USER1 | 24-bit address configuration and dummy-length storage; dummy phase unsupported | +| 0x24 USER2 | Command width/value storage; unsupported command/width rejected at start | +| 0x28 / 0x2c MOSI/MISO_DLEN | 24-bit length fields; MOSI phase unsupported; receive lengths checked at start | +| 0x34 PIN | Reset CS0 configuration; other defined modes unsupported | +| 0x38 SLAVE | TRANS_DONE raw flag, its enable and four-bit operation counter | +| 0x80..0xbc W0..15 | Full-width shared data buffer | + +The SDK register comment calls USER1's address length read-only, while its helper +writes it. This profile qualifies only unchanged 24-bit writes and rejects other +lengths rather than guessing silicon behavior. Reserved bits of supported +registers are ignored/zero; unimplemented registers remain unsupported. Writable +unsupported fields fail unchanged. Receive bytes pack into consecutive low-to-high +byte lanes as the SDK buffer-copy path expects. Unclocked bytes retain deterministic +fixture values, which are not a silicon retention guarantee. + +A successful read completes synchronously, sets TRANS_DONE, increments the +four-bit operation counter and clears CMD. This is a functional abstraction, +without an observable busy interval or clock/CS waveform claim. TRANS_DONE and +its enable are ordinary writable bits; firmware clears raw completion by writing +zero. The level request is raw AND enable, on original-ESP32 matrix source 29. + +The machine owns source 29 while the profile is enabled and rejects host input +injection into that slot. Routing is synchronized after instruction commit; +host writes through the machine bus synchronize immediately. Both CPUs see the +same controller/buffer; software must arbitrate access. CPU-only APP reset +preserves the shared SPI state/request within the existing bounded reset model. +Disabling the attachment relinquishes the source at the next synchronization. +Raw fabric/debug changes require the next machine synchronization; they are not +independently scheduled hardware events. Other sources retain their host-input +behavior. + +## Evidence and limitations + +`test_esp32_flashspi` passes 37,466 assertions covering transfer lengths, high/low buffers, ignored address +padding, completion/enable/counter wrap, unsupported controls/commands, missing +backing/bounds, cache restrictions, unchanged failures, opt-in ownership, +shared bus access, CPU-only reset preservation and routing to both CPUs. A +fetched S32I starts a read, retires once, and then enters its level-2 completion +interrupt on the following step with the correct saved PC. + +`run_flashspi.py` independently parses 27 register reset values and field +positions from pinned SDK headers and passes 384 address/receive/IRQ comparisons, +including crossing 64 KiB boundaries and exact backing-end reads. No vendor +controller implementation is linked into the runtime. + +`run_spi_c.py` compiles original C using the actual ESP32 compiler, the unmodified +SDK register struct and seven unchanged selected SDK helper bodies. Both cores +serialize their shared-controller reads using compiled acquire/exchange/release +atomics. They copy received bytes to native RAM, verify a checksum and clear +completion. Some runs copy a generated code image across a flash-page boundary, +write it to instruction RAM with word stores, and call its compiled entry. + +Evidence: **128 compiled reads, 32 SPI-loaded RAM workers, 32 exact replays and +91,568 retired instructions**. Expected receive bytes, checksum/function results, +stack/window return, cleared lock/source and allocation cleanup are checked. +There are no Python memory callbacks or host worker-PC shortcuts. Selected +helper execution is not the whole SDK initialization/common-command stack, an +actual Espressif ROM, full interrupt-driven driver or bootloader acceptance. +Retired instructions are not physical cycles. No hardware capture was collected. + +## References and reproduction + +Inspected 2026-11-05: ESP32 TRM v5.9 chapter 20, and these external ESP-IDF v4.4.7 +files. They are hash-checked test inputs, not runtime dependencies: + +| Source | SHA-256 | +|---|---| +| [soc/esp32/include/soc/spi_reg.h](https://github.com/espressif/esp-idf/blob/v4.4.7/components/soc/esp32/include/soc/spi_reg.h) | `8fdea138d074362521d9e052894ce3c775326e95006c51ff8a84c2821a715a70` | +| [soc/esp32/include/soc/spi_struct.h](https://github.com/espressif/esp-idf/blob/v4.4.7/components/soc/esp32/include/soc/spi_struct.h) | `97fffd958e992ea25af94e08bb6e5b6c7487671b3e070efa68e80d216db383b2` | +| [hal/esp32/include/hal/spi_flash_ll.h](https://github.com/espressif/esp-idf/blob/v4.4.7/components/hal/esp32/include/hal/spi_flash_ll.h) | `d1331e0d99ff2377e80e7ec4b8f314453345e68d894f5d2b3139c0e8069ae724` | +| [soc/esp32/include/soc/soc.h](https://github.com/espressif/esp-idf/blob/v4.4.7/components/soc/esp32/include/soc/soc.h) | `7a7148c288d7c289c961810e5617fa7b14d105b28349a45f772150b1e0845dd3` | + +The temporary helper extraction retains Espressif's Apache-2.0 notice, and the +register struct is copied unchanged into the temporary build. No SDK +implementation is vendored in this repository. See the +[target investigation](esp32-dual-core.md) for the pinned Espressif toolchain. + +```sh +python3 tests/vendor/xtensa_integer/run_flashspi.py \ + --spi-reg /path/to/spi_reg.h --soc-header /path/to/soc.h +python3 tests/vendor/xtensa_integer/run_spi_c.py \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld \ + --nm /path/to/xtensa-esp32-elf-nm \ + --ll-header /path/to/spi_flash_ll.h --struct-header /path/to/spi_struct.h +``` diff --git a/tests/unit/test_esp32_flashspi.c b/tests/unit/test_esp32_flashspi.c new file mode 100644 index 000000000..727e31500 --- /dev/null +++ b/tests/unit/test_esp32_flashspi.c @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/flashspi.h" +#include "mcu/esp32/machine.h" +static es_status write_reg(esk_esp32_flashspi *s,const unsigned char *image,size_t size,unsigned reg,uint32_t value) +{ return esk_esp32_flashspi_write(s,image,size,1,0x3ff42000u+reg,4,value); } +static void setup(esk_esp32_flashspi *s) +{ + esk_esp32_flashspi_init(s); + CHECK_EQ(write_reg(s,NULL,0,8,0x208400),ES_OK); + CHECK_EQ(write_reg(s,NULL,0,0x1c,0xd0000040),ES_OK); + CHECK_EQ(write_reg(s,NULL,0,0x24,0x70000003),ES_OK); +} +static void all_lengths_and_buffers(void) +{ + unsigned char image[1024];unsigned i,n,high,turn;uint32_t value=0; + for(i=0;i>8))&255u); + for(high=0;high<2;high++) for(n=1;n<=64u-high*32u;n++) for(turn=0;turn<4;turn++) { + esk_esp32_flashspi s;unsigned first=high*32u,address=997u-n; + setup(&s);for(i=0;i<16;i++) CHECK_EQ(write_reg(&s,image,sizeof image,0x80u+i*4u,0xa5a5a5a5),ES_OK); + CHECK_EQ(write_reg(&s,image,sizeof image,0x1c,0xd0000040u|(high<<24)),ES_OK); + CHECK_EQ(write_reg(&s,image,sizeof image,4,(address<<8)|(turn*85u)),ES_OK); + CHECK_EQ(write_reg(&s,image,sizeof image,0x2c,n*8u-1u),ES_OK); + CHECK_EQ(write_reg(&s,image,sizeof image,0,0x40000),ES_OK);CHECK(esk_esp32_flashspi_irq(&s)); + for(i=0;i<64;i++) { + uint32_t wanted=i>=first && i>((i&3u)*8u))&255u,wanted); + } + CHECK_EQ(esk_esp32_flashspi_read(&s,0x3ff42000,4,&value),ES_OK);CHECK_EQ(value,0); + CHECK_EQ(s.slave,0x800210); + CHECK_EQ(write_reg(&s,image,sizeof image,0x38,0x200),ES_OK);CHECK(!esk_esp32_flashspi_irq(&s)); + CHECK_EQ(write_reg(&s,image,sizeof image,0x38,0x10),ES_OK);CHECK(!esk_esp32_flashspi_irq(&s)); + CHECK_EQ(write_reg(&s,image,sizeof image,0x38,0x210),ES_OK);CHECK(esk_esp32_flashspi_irq(&s)); + } +} +static void failures_are_atomic(void) +{ + esk_esp32_flashspi s,before;unsigned char image[64];unsigned i;uint32_t value=0xdeadbeef; + const unsigned regs[]={8,0x18,0x1c,0x20,0x34,0x38,0x10,0x100}; + const uint32_t values[]={0,0,0xd00000c0,0,7,0x40000000,0,0}; + memset(image,0x5a,sizeof image);setup(&s); + CHECK_EQ(write_reg(&s,image,sizeof image,0x2c,511),ES_OK); + for(i=0;i>23)&15u,1); + for(i=0;i<31;i++) CHECK_EQ(write_reg(&s,image,sizeof image,0,0x40000),ES_OK); + CHECK_EQ((s.slave>>23)&15u,0); + CHECK_EQ(write_reg(&s,image,sizeof image,4,1u<<8),ES_OK);before=s; + CHECK_EQ(write_reg(&s,image,sizeof image,0,0x40000),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&s,&before,sizeof s),0); + CHECK_EQ(write_reg(&s,image,sizeof image,4,0),ES_OK); + CHECK_EQ(write_reg(&s,image,sizeof image,0x2c,512),ES_OK);before=s; + CHECK_EQ(write_reg(&s,image,sizeof image,0,0x40000),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&s,&before,sizeof s),0); + CHECK_EQ(write_reg(&s,image,sizeof image,0x24,0x7000009f),ES_OK);before=s; + CHECK_EQ(write_reg(&s,image,sizeof image,0,0x40000),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&s,&before,sizeof s),0); + CHECK_EQ(esk_esp32_flashspi_read(&s,0x3ff42001,4,&value),ES_EINVAL);CHECK_EQ(value,0xdeadbeefu); + CHECK_EQ(esk_esp32_flashspi_read(&s,0x3ff42000,1,&value),ES_EINVAL);CHECK_EQ(value,0xdeadbeefu); + CHECK_EQ(esk_esp32_flashspi_read(&s,0x3ff43000,4,&value),ES_EUNSUPPORTED);CHECK_EQ(value,0xdeadbeefu); + CHECK_EQ(esk_esp32_flashspi_read(NULL,0,4,&value),ES_EINVAL); + CHECK_EQ(esk_esp32_flashspi_write(NULL,NULL,0,0,0,4,0),ES_EINVAL); +} +static void native_bus_irq_and_opt_in(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_esp32_fabric *f;esk_xtensa_memory bus[2];uint32_t value=0;int irq=77; + unsigned char *image=(unsigned char *)malloc(65536);unsigned i;esk_xtensa_windows *cpu; + CHECK(image!=NULL);if(!image) return;for(i=0;i<65536;i++) image[i]=(unsigned char)(i&255u); + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK);f=esk_esp32_machine_fabric(m); + CHECK_EQ(esk_esp32_fabric_spi1_configure(f,1),ES_EUNSUPPORTED); + CHECK_EQ(esk_esp32_fabric_spi1_irq(f,&irq),ES_EUNSUPPORTED);CHECK_EQ(irq,77); + CHECK_EQ(esk_esp32_fabric_flash(f,image,65536),ES_OK); + bus[0]=esk_esp32_machine_bus(m,0);bus[1]=esk_esp32_machine_bus(m,1); + CHECK_EQ(bus[0].read(bus[0].ctx,0x3ff42000,4,&value),ES_EUNSUPPORTED); + CHECK_EQ(esk_esp32_fabric_spi1_configure(f,1),ES_OK); + CHECK_EQ(esk_esp32_machine_source(m,29,1),ES_EUNSUPPORTED); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff00178,4,19),ES_OK); /* SPI1 -> PRO level 2 */ + cpu=esk_esp32_machine_core(m,0);cpu->irq.enabled=1; + esk_esp32_machine_core(m,1)->irq.enabled=1; + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff0028c,4,19),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff42008,4,0x208400),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff4201c,4,0xd0000040),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff42024,4,0x70000003),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff4202c,4,31),ES_OK); + CHECK_EQ(bus[1].write(bus[1].ctx,0x3ff42004,4,0x1234ff),ES_OK); + CHECK_EQ(bus[1].write(bus[1].ctx,0x3ff42000,4,0x40000),ES_OK); + CHECK_EQ(bus[0].read(bus[0].ctx,0x3ff42080,4,&value),ES_OK);CHECK_EQ(value,0x37363534); + CHECK_EQ(esk_esp32_fabric_spi1_irq(f,&irq),ES_OK);CHECK_EQ(irq,1);CHECK(cpu->irq.lines&(1u<<19)); + CHECK(esk_esp32_machine_core(m,1)->irq.lines&(1u<<19)); + CHECK_EQ(esk_esp32_machine_app_reset_configure(m,1),ES_OK); + CHECK_EQ(bus[0].read(bus[0].ctx,0x3ff42080,4,&value),ES_OK);CHECK_EQ(value,0x37363534); + CHECK_EQ(esk_esp32_fabric_spi1_irq(f,&irq),ES_OK);CHECK_EQ(irq,1); + CHECK_EQ(bus[0].read(bus[0].ctx,0x3ff000ec,4,&value),ES_OK);CHECK(value&(1u<<29)); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff42038,4,0x200),ES_OK);CHECK(!(cpu->irq.lines&(1u<<19))); + CHECK_EQ(bus[1].fetch(bus[1].ctx,0x3ff42080,3,&value),ES_EUNSUPPORTED); + CHECK_EQ(bus[1].compare_exchange(bus[1].ctx,0x3ff42080,0,0,&value),ES_EUNSUPPORTED); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff10000,4,0),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff00040,4,0),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff00040,4,16),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff00040,4,24),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff42000,4,0x40000),ES_EUNSUPPORTED); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff00040,4,16),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff12000,4,0),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff00058,4,0),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff00058,4,16),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff00058,4,24),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff42000,4,0x40000),ES_EUNSUPPORTED); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff00058,4,16),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff42000,4,0x40000),ES_OK); + CHECK(cpu->irq.lines&(1u<<19)); + CHECK_EQ(esk_esp32_fabric_spi1_configure(f,0),ES_OK); + CHECK_EQ(esk_esp32_machine_source(m,29,0),ES_OK);CHECK(!(cpu->irq.lines&(1u<<19))); + CHECK_EQ(esk_esp32_machine_source(m,29,1),ES_OK);CHECK(cpu->irq.lines&(1u<<19)); + esk_esp32_machine_destroy(m);free(image);CHECK_EQ(a.live_bytes,0); +} +static void instruction_commit_then_interrupt(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_esp32_fabric *f;esk_xtensa_windows *w; + esk_xtensa_memory bus;uint32_t value=0;unsigned event=99;esk_xtensa_fault fault; + const unsigned char code[3]={0x32,0x62,0}; /* s32i a3,a2,0 */ + unsigned char *image=(unsigned char *)malloc(65536);CHECK(image!=NULL);if(!image) return;memset(image,0x5a,65536); + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK);f=esk_esp32_machine_fabric(m); + CHECK_EQ(esk_esp32_fabric_flash(f,image,65536),ES_OK);CHECK_EQ(esk_esp32_fabric_spi1_configure(f,1),ES_OK); + CHECK_EQ(esk_esp32_fabric_load(f,0,0x40080000,code,sizeof code),ES_OK); + CHECK_EQ(esk_esp32_machine_configure(m,0,0x40080000),ES_OK);w=esk_esp32_machine_core(m,0); + w->irq.enabled=1;w->irq.intenable=1u<<19;w->irq.vectors[2]=0x40084000; + w->cpu.a[2]=0x3ff42000;w->cpu.a[3]=0x40000; + bus=esk_esp32_machine_bus(m,0); + CHECK_EQ(bus.write(bus.ctx,0x3ff00178,4,19),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff42008,4,0x208400),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff4201c,4,0xd0000040),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff42024,4,0x70000003),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff4202c,4,31),ES_OK); + CHECK_EQ(esk_esp32_machine_run_core(m,0,1),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK);CHECK_EQ(event,0); + CHECK_EQ(w->cpu.pc,0x40080003);CHECK_EQ(w->cpu.a[3],0x40000);CHECK_EQ(esk_esp32_machine_retired(m,0),1); + CHECK(w->irq.lines&(1u<<19)); + CHECK_EQ(bus.read(bus.ctx,0x3ff42080,4,&value),ES_OK);CHECK_EQ(value,0x5a5a5a5a); + CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK);CHECK_EQ(event,12); + CHECK_EQ(w->cpu.pc,0x40084000);CHECK_EQ(w->irq.epc[2],0x40080003);CHECK_EQ(esk_esp32_machine_retired(m,0),1); + CHECK_EQ(bus.write(bus.ctx,0x3ff42038,4,0x200),ES_OK);CHECK(!(w->irq.lines&(1u<<19))); + esk_esp32_machine_destroy(m);free(image);CHECK_EQ(a.live_bytes,0); +} +TEST_MAIN_BEGIN + (void)t_str_eq;RUN(all_lengths_and_buffers);RUN(failures_are_atomic);RUN(native_bus_irq_and_opt_in);RUN(instruction_commit_then_interrupt); +TEST_MAIN_END diff --git a/tests/vendor/README.md b/tests/vendor/README.md index e9e147b5d..9ef9e58cc 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -163,3 +163,12 @@ See [context register scope and reproduction](../../docs/engineering/esp32-conte compiled path keeps caller windows resident; native/assembler tests cover the missing-caller exception. Complete vendor alloca-handler restoration remains unqualified. Reproduction is in the context register contract above. + +`xtensa_integer/run_flashspi.py` takes pinned ESP-IDF v4.4.7 `--spi-reg` and +`--soc-header`: 27 parsed register defaults and 384 read/address/IRQ comparisons. +`run_spi_c.py` takes actual ESP32 compiler/objcopy/linker/nm plus pinned +`--ll-header` and `--struct-header`. Seven unchanged SDK helper bodies drive +128 native-bus SPI1 reads; 32 also load and call a RAM worker. Thirty-two exact +replays retire 91,568 instructions. This is an opt-in single-line 03h read profile, +not flash programming, DMA, physical timing or actual ROM/SDK boot. See the +[SPI1 read contract and reproduction](../../docs/engineering/esp32-spi1-reads.md). diff --git a/tests/vendor/xtensa_integer/run_controls.py b/tests/vendor/xtensa_integer/run_controls.py index e66acbcf7..5f8f3bf4d 100644 --- a/tests/vendor/xtensa_integer/run_controls.py +++ b/tests/vendor/xtensa_integer/run_controls.py @@ -38,7 +38,7 @@ def main(): with tempfile.TemporaryDirectory(prefix='embsim-controls-') as tmp: lib = Path(tmp) / 'controls.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_flash_c.py b/tests/vendor/xtensa_integer/run_flash_c.py index 19a96bfe3..384575a75 100644 --- a/tests/vendor/xtensa_integer/run_flash_c.py +++ b/tests/vendor/xtensa_integer/run_flash_c.py @@ -68,7 +68,7 @@ def compile_code(name, source, base, entry, flags=()): flash_image = bytes(image) lib = out / 'flash.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_flashmap.py b/tests/vendor/xtensa_integer/run_flashmap.py index e753da9b0..df947b7a1 100644 --- a/tests/vendor/xtensa_integer/run_flashmap.py +++ b/tests/vendor/xtensa_integer/run_flashmap.py @@ -51,7 +51,7 @@ def initial(name): with tempfile.TemporaryDirectory(prefix='embsim-flashmap-') as tmp: lib = Path(tmp) / 'flashmap.so' subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', - '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), str(ROOT / 'core/mcu/esp32/flashmap.c'), + '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), str(ROOT / 'core/mcu/esp32/flashmap.c'), str(ROOT / 'core/mcu/esp32/flashspi.c'), str(ROOT / 'core/util/alloc.c'), '-o', str(lib)], check=True) api = C.CDLL(str(lib)); pointer = C.c_void_p def bind(name, result, *params): diff --git a/tests/vendor/xtensa_integer/run_flashspi.py b/tests/vendor/xtensa_integer/run_flashspi.py new file mode 100644 index 000000000..8ee3e7102 --- /dev/null +++ b/tests/vendor/xtensa_integer/run_flashspi.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""Pinned ESP32 SPI1 register defaults/fields and independent READ-byte oracle. + +No timing, SPI pins, flash programming or complete SDK/board qualification. +""" +import argparse +import ctypes as C +from pathlib import Path +import random +import re +import subprocess +import tempfile +from run_execute import ROOT +from run_matrix import read_header + + +class SPI(C.Structure): + _fields_=[(n,C.c_uint32) for n in ['addr','ctrl','user','user1','user2','mosi','miso','slave']]+[('buffer',C.c_uint32*16)] + + +def main(): + ap=argparse.ArgumentParser(description=__doc__) + ap.add_argument('--spi-reg',type=Path,required=True);ap.add_argument('--soc-header',type=Path,required=True) + ap.add_argument('--cc',default='cc');args=ap.parse_args();rng=random.Random(0x5b11) + header=read_header(args.spi_reg,'8fdea138d074362521d9e052894ce3c775326e95006c51ff8a84c2821a715a70') + soc=read_header(args.soc_header,'7a7148c288d7c289c961810e5617fa7b14d105b28349a45f772150b1e0845dd3') + base=int(re.search(r'#define\s+DR_REG_SPI1_BASE\s+(\S+)',soc)[1],0) + assert int(re.search(r'#define\s+ETS_SPI1_INTR_SOURCE\s+(\d+)',soc)[1])==29 + def reg(name): + return base+int(re.search(r'#define\s+SPI_'+name+r'_REG\(i\)\s+\(REG_SPI_BASE\(i\)\s*\+\s*(0x[0-9a-fA-F]+)\)',header)[1],16) + def initial(name): + start=re.search(r'^#define\s+SPI_'+name+r'_REG\(i\)[^\n]*',header,re.M).end() + following=re.search(r'^#define\s+SPI_\w+_REG\(i\)',header[start:],re.M) + block=header[start:start+following.start()] if following else header[start:] + value=0 + for high,low,radix,digits in re.findall(r";bitpos:\[(\d+)(?::(\d+))?\]\s*;default:\s*\d+'([bhd])([0-9a-fA-F_]+)",block): + value|=int(digits.replace('_',''),{'b':2,'h':16,'d':10}[radix])<ctrl.fastrd_mode=0; + dev->user.val=0xd0000040 | (high<<24); + spi_flash_ll_set_command(dev,3,8); + spi_flash_ll_set_addr_bitlen(dev,24); + spi_flash_ll_set_usr_address(dev,address,24); + spi_flash_ll_set_miso_bitlen(dev,length*8); + spi_flash_ll_set_mosi_bitlen(dev,0); + spi_flash_ll_user_start(dev); + while(!spi_flash_ll_cmd_is_done(dev)) { } + unsigned checksum=0; + for(unsigned i=0;idata_buf[(high?8:0)+i/4]>>((i&3)*8))&255; + out[i]=byte; + checksum=checksum*33+byte; + } + dev->slave.trans_done=0; + if(entry) { + volatile unsigned *destination=(volatile unsigned *)(entry&~4095u); + unsigned *words=(unsigned *)out; + for(unsigned i=0;i\n#include \n#include "vendor-spi-struct.h"\n'+'\n'.join(helpers)+SOURCE + code,start=compile_code('operation',source,0x40080000,'operation') + image=bytearray(rng.getrandbits(8) for _ in range(4*65536));entries=[];lengths=[];tags=[0x12345678,0x98765432] + for cpu in range(2): + worker,entry=compile_code('worker'+str(cpu),WORKER,0x40088000+cpu*4096,'worker',['-DTAG='+hex(tags[cpu])+'u']) + worker+=bytes((-len(worker))%4);assert 16>7))&0xffffffff + params.append([address,size,0x3ffb8000+cpu*256,entry,value,high]);expected.append((wanted,data)) + previous=None + for replay in range(2): + a=Allocator();init(C.byref(a),None);m=pointer();assert create(C.byref(a),C.byref(m))==0 + try: + ram=fabric(m);bus=bus_get(m,0) + assert flash(ram,C.c_char_p(image),len(image))==0 and spi(ram,1)==0 + assert load(ram,0,0x40080000,C.c_char_p(code),len(code))==0 + assert bus.write(bus.ctx,0x3ff00030,4,1)==0 + states=[] + for cpu in range(2): + assert configure(m,cpu,start)==0 + w=core(m,cpu).contents;w.ps|=1<<16;w.cpu.a[1]=0x3ffd0000+cpu*4096;w.cpu.a[4]=0x7ffffff0 + for n,arg in enumerate(params[cpu]):w.cpu.a[6+n]=arg + states.append(w);assert run(m,cpu,1)==0 + done=[False,False];event=C.c_uint();fault=Fault();trace=[];worker_steps=[0,0] + for tick in range(30000): + cpu=(tick+trial)%2;w=states[cpu] + if done[cpu]:continue + if w.cpu.pc==0x7ffffff0: + done[cpu]=True;assert run(m,cpu,0)==0 + if all(done):break + continue + pc=w.cpu.pc + status=step(m,cpu,C.byref(event),C.byref(fault)) + assert status==0 and event.value==0,(status,cpu,hex(fault.pc),hex(fault.opcode),fault.kind,event.value) + if 0x40088000<=pc<0x4008a000:worker_steps[cpu]+=1 + trace.append((cpu,pc,w.cpu.pc));total+=1 + assert all(done) + read=C.c_uint32() + for cpu,w in enumerate(states): + assert w.cpu.a[6]==expected[cpu][0] and w.base==0 and w.start==1 + assert w.cpu.a[1]==0x3ffd0000+cpu*4096 + actual=bytearray() + for n in range(params[cpu][1]): + assert bus.read(bus.ctx,params[cpu][2]+n,1,C.byref(read))==0;actual.append(read.value) + assert actual==expected[cpu][1] + assert bool(worker_steps[cpu])==bool(params[cpu][3]);loaded+=bool(worker_steps[cpu]);calls+=1 + assert bus.read(bus.ctx,0x3ffc1000,4,C.byref(read))==0 and read.value==0 + assert bus.read(bus.ctx,0x3ff000ec,4,C.byref(read))==0 and not read.value&(1<<29) + result=trace,[w.cpu.a[6] for w in states] + if replay:assert result==previous + else:previous=result + finally:destroy(m) + assert a.live_bytes==0 + print(f'{calls} actual ESP32 SPI1 C reads, {loaded} SPI-loaded RAM workers, 32 exact replays, {total} retired instructions; seven unchanged SDK helper bodies') + + +if __name__=='__main__':main() diff --git a/tests/vendor/xtensa_integer/run_tls_c.py b/tests/vendor/xtensa_integer/run_tls_c.py index 78db2ee2e..52c681f6f 100644 --- a/tests/vendor/xtensa_integer/run_tls_c.py +++ b/tests/vendor/xtensa_integer/run_tls_c.py @@ -83,7 +83,7 @@ def main(): assert int.from_bytes(template[symbols['zero']:symbols['zero'] + 4], 'little') == 0 lib = out / 'tls.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) From d4e34ae3dc84a2c23d72fa04f1f16be9369373de Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 11:23:48 +0000 Subject: [PATCH 23/57] Add explicit W25Q32JV programming and erase profile --- CHANGELOG.md | 17 ++- core/mcu/esp32/fabric.c | 15 ++ core/mcu/esp32/fabric.h | 17 ++- core/mcu/esp32/flashmap.c | 41 +++++- core/mcu/esp32/flashmap.h | 12 +- core/mcu/esp32/flashspi.c | 30 +++- core/mcu/esp32/flashspi.h | 12 +- core/mcu/esp32/w25q32.c | 68 +++++++++ core/mcu/esp32/w25q32.h | 28 ++++ docs/STATUS.md | 19 ++- docs/engineering/esp32-dual-core.md | 17 ++- docs/engineering/esp32-flash-programming.md | 144 +++++++++++++++++++ docs/engineering/esp32-spi1-reads.md | 8 ++ tests/unit/test_esp32_w25q32.c | 126 ++++++++++++++++ tests/vendor/README.md | 10 ++ tests/vendor/xtensa_integer/run_controls.py | 2 +- tests/vendor/xtensa_integer/run_flash_c.py | 2 +- tests/vendor/xtensa_integer/run_flashmap.py | 2 +- tests/vendor/xtensa_integer/run_ipi_c.py | 2 +- tests/vendor/xtensa_integer/run_machine_c.py | 2 +- tests/vendor/xtensa_integer/run_reset_c.py | 2 +- tests/vendor/xtensa_integer/run_spi_c.py | 68 +++++++-- tests/vendor/xtensa_integer/run_tls_c.py | 2 +- tests/vendor/xtensa_integer/run_w25q32.py | 96 +++++++++++++ 24 files changed, 706 insertions(+), 36 deletions(-) create mode 100644 core/mcu/esp32/w25q32.c create mode 100644 core/mcu/esp32/w25q32.h create mode 100644 docs/engineering/esp32-flash-programming.md create mode 100644 tests/unit/test_esp32_w25q32.c create mode 100644 tests/vendor/xtensa_integer/run_w25q32.py diff --git a/CHANGELOG.md b/CHANGELOG.md index cf7b6b993..4ff5c3624 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Experimental explicit W25Q32JV flash programming + +- Added an explicitly selected, unprotected W25Q32JV-IQ 4 MiB profile with + write-enable/BUSY status, page programming/wrap and sector/block/chip erase. + External microseconds drive conservative datasheet-maximum durations. +- Added SPI1 USER transmit/receive integration and post-completion cache flush + enforcement. Default immutable behavior remains available; the requested + board's actual chip is still unverified. Protection, suspend, electrical + timing, full chip behavior and ROM/SDK boot remain unimplemented. +- Native tests and a datasheet-pinned independent oracle pass. Actual ESP32 C + programs code, polls BUSY and loads/runs it: 32 programmed workers, 32 exact + replays and 125,856 retired instructions. + ### Experimental ESP32 SPI1 flash reads - Added an opt-in USER 03h single-line read path to the installed immutable @@ -12,8 +25,8 @@ versions follow semantic versioning. There is no release yet. high/low data buffers. Both caches must be disabled for transfers. - Added completion/counter state and SPI1 matrix-source ownership, synchronized after instruction commit. Unsupported commands/modes and out-of-extent reads - fail unchanged. Programming, erase, DMA, timing and real ROM/SDK boot remain - unimplemented. + fail unchanged. This default read-only attachment omits programming/erase; + DMA, timing and real ROM/SDK boot remain unimplemented. - Added 37,466 native assertions, 27 parsed vendor register defaults/384 reference comparisons and actual ESP32 C with seven unchanged SDK helper bodies: 128 reads, 32 SPI-loaded RAM workers, 32 replays and 91,568 retired instructions. diff --git a/core/mcu/esp32/fabric.c b/core/mcu/esp32/fabric.c index 962a0f02b..0c4d81345 100644 --- a/core/mcu/esp32/fabric.c +++ b/core/mcu/esp32/fabric.c @@ -134,6 +134,21 @@ es_status esk_esp32_fabric_spi1_irq(const esk_esp32_fabric *f,int *asserted) if(!f || !asserted) return ES_EINVAL; return f->flash ? esk_esp32_flashmap_spi1_irq(f->flash,asserted) : ES_EUNSUPPORTED; } +es_status esk_esp32_fabric_w25q32_configure(esk_esp32_fabric *f,int enabled) +{ + if(!f) return ES_EINVAL; + return f->flash ? esk_esp32_flashmap_w25q32_configure(f->flash,enabled) : ES_EUNSUPPORTED; +} +es_status esk_esp32_fabric_flash_advance(esk_esp32_fabric *f,uint64_t microseconds) +{ + if(!f) return ES_EINVAL; + return f->flash ? esk_esp32_flashmap_flash_advance(f->flash,microseconds) : ES_EUNSUPPORTED; +} +es_status esk_esp32_fabric_flash_deadline(const esk_esp32_fabric *f,uint64_t *microseconds) +{ + if(!f || !microseconds) return ES_EINVAL; + return f->flash ? esk_esp32_flashmap_flash_deadline(f->flash,microseconds) : ES_EUNSUPPORTED; +} es_status esk_esp32_fabric_remap(esk_esp32_fabric *f,unsigned cpu,int enabled) { if(!f || cpu>1) return ES_EINVAL; diff --git a/core/mcu/esp32/fabric.h b/core/mcu/esp32/fabric.h index 87d3aa6e6..8fbbe98ac 100644 --- a/core/mcu/esp32/fabric.h +++ b/core/mcu/esp32/fabric.h @@ -21,15 +21,28 @@ es_status esk_esp32_fabric_rom(esk_esp32_fabric *f,unsigned which,const unsigned * Tables start invalid by fixture policy. Edit while target cache disabled, * flush via clear/set FLUSH_ENA, then enable. No cache tags/timing, SPI writes, * PSRAM/high-PID/lock/special modes or physical flash-part identity claim. - * Replacement requires both caches disabled and preserves mappings; allocation + * Replacement requires both caches disabled and a detached named flash-device + * profile, and preserves mappings; allocation * failure leaves the old image/state intact. Host calls only between turns. */ es_status esk_esp32_fabric_flash(esk_esp32_fabric *f,const unsigned char *data,size_t len); /* Opt-in SPI1 USER 03h read path to the installed immutable backing. Both - * caches must be disabled during transfers. No chip ID/program/erase/timing. + * caches must be disabled during transfers. The default attachment omits + * chip ID/program/erase/timing; the named-part option below extends it. * Enabling/disabling preserves SPI state; CPU-only reset also preserves it. */ es_status esk_esp32_fabric_spi1_configure(esk_esp32_fabric *f,int enabled); /* ES_EUNSUPPORTED means disabled/unowned; output is unchanged on failure. */ es_status esk_esp32_fabric_spi1_irq(const esk_esp32_fabric *f,int *asserted); +/* Explicit W25Q32JV-IQ unprotected 4MiB attachment, with only the subset in + * w25q32.h. No assertion that this is the requested board's actual flash. + * Both caches disabled for configuration/transfers; no detach while busy. + * Selected part state is reset on attach/detach, not on CPU reset. Backing + * replacement is rejected while attached. Advance external chip time only + * between machine turns; APP clock gates do not stop this independent clock. + * Program/erase require a fresh cache flush after completion before re-enable. */ +es_status esk_esp32_fabric_w25q32_configure(esk_esp32_fabric *f,int enabled); +es_status esk_esp32_fabric_flash_advance(esk_esp32_fabric *f,uint64_t microseconds); +/* Zero deadline means idle; failed calls leave output unchanged. */ +es_status esk_esp32_fabric_flash_deadline(const esk_esp32_fabric *f,uint64_t *microseconds); /* Host-controlled boot-remap bit, not a DPORT register model. */ es_status esk_esp32_fabric_remap(esk_esp32_fabric *f,unsigned cpu,int enabled); /* All-or-nothing RAM image write through the selected core's address view. */ diff --git a/core/mcu/esp32/flashmap.c b/core/mcu/esp32/flashmap.c index 21470cb16..5fb42252d 100644 --- a/core/mcu/esp32/flashmap.c +++ b/core/mcu/esp32/flashmap.c @@ -4,11 +4,13 @@ * Original functional translation implementation, no vendor controller code. */ #include "mcu/esp32/flashmap.h" #include "mcu/esp32/flashspi.h" +#include "mcu/esp32/w25q32.h" #include struct esk_esp32_flashmap { esk_alloc *a;unsigned char *image;size_t size; uint32_t table[2][256],ctrl[2],ctrl1[2],dirty[2]; esk_esp32_flashspi spi1;int spi1_enabled; + esk_esp32_w25q32 nor;int nor_enabled; }; es_status esk_esp32_flashmap_create(esk_alloc *a,esk_esp32_flashmap **out) { @@ -33,11 +35,40 @@ es_status esk_esp32_flashmap_install(esk_esp32_flashmap *f,const unsigned char * { unsigned char *copy; if(!f || !data || !len || (len&65535u) || len>0x1000000u) return ES_EINVAL; - if((f->ctrl[0]|f->ctrl[1])&8u) return ES_EUNSUPPORTED; + if(f->nor_enabled || ((f->ctrl[0]|f->ctrl[1])&8u)) return ES_EUNSUPPORTED; copy=(unsigned char *)esk_malloc(f->a,len);if(!copy) return ES_ENOMEM; memcpy(copy,data,len);if(f->image) esk_free(f->a,f->image,f->size); f->image=copy;f->size=len;f->dirty[0]=f->dirty[1]=1;return ES_OK; } +static es_status chip_command(void *ctx,unsigned command,int has_address,uint32_t address,const unsigned char *tx,size_t tx_len,unsigned char *rx,size_t rx_len) +{ + esk_esp32_flashmap *f=(esk_esp32_flashmap *)ctx;uint64_t before=f->nor.remaining_us; + es_status st=esk_esp32_w25q32_command(&f->nor,f->image,f->size,command,has_address,address,tx,tx_len,rx,rx_len); + if(st==ES_OK && !before && f->nor.remaining_us) f->dirty[0]=f->dirty[1]=1; + return st; +} +es_status esk_esp32_flashmap_w25q32_configure(esk_esp32_flashmap *f,int enabled) +{ + if(!f) return ES_EINVAL; + if(f->size!=0x400000u || ((f->ctrl[0]|f->ctrl[1])&8u) || f->nor.remaining_us) return ES_EUNSUPPORTED; + if(f->nor_enabled!=(enabled!=0)) { esk_esp32_w25q32_init(&f->nor);f->nor_enabled=enabled!=0; } + return ES_OK; +} +es_status esk_esp32_flashmap_flash_advance(esk_esp32_flashmap *f,uint64_t microseconds) +{ + uint64_t before;es_status st; + if(!f) return ES_EINVAL; + if(!f->nor_enabled) return ES_EUNSUPPORTED; + before=f->nor.remaining_us;st=esk_esp32_w25q32_advance(&f->nor,f->image,f->size,microseconds); + if(st==ES_OK && before && !f->nor.remaining_us) f->dirty[0]=f->dirty[1]=1; + return st; +} +es_status esk_esp32_flashmap_flash_deadline(const esk_esp32_flashmap *f,uint64_t *microseconds) +{ + if(!f || !microseconds) return ES_EINVAL; + if(!f->nor_enabled) return ES_EUNSUPPORTED; + *microseconds=f->nor.remaining_us;return ES_OK; +} es_status esk_esp32_flashmap_spi1_configure(esk_esp32_flashmap *f,int enabled) { if(!f) return ES_EINVAL; @@ -73,7 +104,11 @@ es_status esk_esp32_flashmap_reg_write(esk_esp32_flashmap *f,uint32_t address,un { unsigned cpu,kind,index;uint32_t next;es_status st; if(!f) return ES_EINVAL; - if(f->spi1_enabled && address>=0x3ff42000 && address<0x3ff42400) return esk_esp32_flashspi_write(&f->spi1,f->image,f->size,((f->ctrl[0]|f->ctrl[1])&8u)==0,address,bytes,value); + if(f->spi1_enabled && address>=0x3ff42000 && address<0x3ff42400) { + int idle=((f->ctrl[0]|f->ctrl[1])&8u)==0; + return f->nor_enabled ? esk_esp32_flashspi_device_write(&f->spi1,chip_command,f,idle,address,bytes,value) : + esk_esp32_flashspi_write(&f->spi1,f->image,f->size,idle,address,bytes,value); + } st=decode(address,bytes,&cpu,&kind,&index);if(st!=ES_OK) return st; if(kind==0) { if((value&~UINT32_C(0x1ff)) || (f->ctrl[cpu]&8u)) return ES_EUNSUPPORTED; @@ -83,7 +118,7 @@ es_status esk_esp32_flashmap_reg_write(esk_esp32_flashmap *f,uint32_t address,un /* Reject unsupported defined RW fields. RO/reserved bits are ignored. */ if(value&UINT32_C(0x10fc4)) return ES_EUNSUPPORTED; if(flush && ((f->ctrl[cpu]|value)&8u)) return ES_EUNSUPPORTED; - if((value&8u) && f->dirty[cpu]) return ES_EUNSUPPORTED; + if((value&8u) && (f->dirty[cpu] || (f->nor_enabled && f->nor.remaining_us))) return ES_EUNSUPPORTED; next=(value&24u)|(f->ctrl[cpu]&32u); if(!(value&16u)) next&=~32u; if(flush) { next|=32u;f->dirty[cpu]=0; } diff --git a/core/mcu/esp32/flashmap.h b/core/mcu/esp32/flashmap.h index dd4af9421..d9a9688bb 100644 --- a/core/mcu/esp32/flashmap.h +++ b/core/mcu/esp32/flashmap.h @@ -3,15 +3,16 @@ #ifndef ESK_ESP32_FLASHMAP_H #define ESK_ESP32_FLASHMAP_H #include "util/alloc.h" -/* Read-only original-ESP32 flash/MMU functional profile, PID0/1 normal mode. +/* Default read-only original-ESP32 flash/MMU functional profile, PID0/1 normal mode. * Per-core 256-entry tables, 64K pages, access masks and explicit flush/enable - * sequence. No cache tags/replacement/latency, physical SPI/JEDEC programming, + * sequence. No cache tags/replacement/latency, physical SPI/pin timing, full flash protocol/protection, * PSRAM, high PIDs, lock/special/page modes, pool reuse or fault IRQ modeling. * To keep immutable-image reads qualified without cache-coherence guesses, * table edits require the destination cache disabled and a fresh flush before * enabling. Flash replacement requires both caches disabled. Unsupported modes * fail unchanged. Optional SPI1 USER reads use the same immutable backing - * under the separate flashspi.h contract. Initial invalid entries are fixture + * under the separate flashspi.h contract. An explicitly attached W25Q32 profile + * adds timed program/erase; its header lists the deliberately bounded subset. Initial invalid entries are fixture * policy, not silicon reset. * No concurrent callers or re-entry. */ typedef struct esk_esp32_flashmap esk_esp32_flashmap; @@ -22,6 +23,11 @@ void esk_esp32_flashmap_destroy(esk_esp32_flashmap *f); es_status esk_esp32_flashmap_install(esk_esp32_flashmap *f,const unsigned char *data,size_t len); /* Explicit immutable SPI1 USER 03h read attachment; see flashspi.h. */ es_status esk_esp32_flashmap_spi1_configure(esk_esp32_flashmap *f,int enabled); +/* Explicit W25Q32JV-IQ unprotected 4MiB profile, not a board-part assertion. + * Both caches disabled; cannot detach or replace backing while busy. */ +es_status esk_esp32_flashmap_w25q32_configure(esk_esp32_flashmap *f,int enabled); +es_status esk_esp32_flashmap_flash_advance(esk_esp32_flashmap *f,uint64_t microseconds); +es_status esk_esp32_flashmap_flash_deadline(const esk_esp32_flashmap *f,uint64_t *microseconds); es_status esk_esp32_flashmap_spi1_irq(const esk_esp32_flashmap *f,int *asserted); es_status esk_esp32_flashmap_reg_read(const esk_esp32_flashmap *f,uint32_t address,unsigned bytes,uint32_t *value); es_status esk_esp32_flashmap_reg_write(esk_esp32_flashmap *f,uint32_t address,unsigned bytes,uint32_t value); diff --git a/core/mcu/esp32/flashspi.c b/core/mcu/esp32/flashspi.c index d48fe9b43..5b8d45096 100644 --- a/core/mcu/esp32/flashspi.c +++ b/core/mcu/esp32/flashspi.c @@ -45,7 +45,25 @@ static es_status transfer(esk_esp32_flashspi *s,const unsigned char *image,size_ s->slave=(s->slave&~UINT32_C(0x7800000))|((((s->slave>>23)+1u)&15u)<<23)|16u; return ES_OK; } -es_status esk_esp32_flashspi_write(esk_esp32_flashspi *s,const unsigned char *image,size_t size,int caches_disabled,uint32_t address,unsigned bytes,uint32_t value) +static es_status device_transfer(esk_esp32_flashspi *s,esk_esp32_flashspi_command_fn command,void *ctx,int caches_disabled) +{ + unsigned char tx[64],rx[64];unsigned i,first_tx=(s->user&0x2000000u) ? 32u : 0u,first_rx=(s->user&0x1000000u) ? 32u : 0u; + uint32_t tx_bits=(s->user&0x8000000u) ? s->mosi+1u : 0u,rx_bits=(s->user&0x10000000u) ? s->miso+1u : 0u; + es_status st; + if(!command || !caches_disabled || (s->ctrl&0x2000u) || !(s->user&0x80000000u) || (s->user&0x20000000u) || + (s->user2>>28)!=7 || tx_bits%8u || rx_bits%8u || tx_bits>512u-first_tx*8u || rx_bits>512u-first_rx*8u || + ((s->user&0x8000000u) && (s->user&0x10000000u))) return ES_EUNSUPPORTED; + for(i=0;ibuffer[n/4u]>>((n&3u)*8u))&255u); } + st=command(ctx,s->user2&65535u,(s->user&0x40000000u)!=0,s->addr>>8,tx,tx_bits/8u,rx,rx_bits/8u); + if(st!=ES_OK) return st; + for(i=0;ibuffer[n/4u]=(s->buffer[n/4u]&~(UINT32_C(255)<slave=(s->slave&~UINT32_C(0x7800000))|((((s->slave>>23)+1u)&15u)<<23)|16u;return ES_OK; +} +static es_status write_impl(esk_esp32_flashspi *s,const unsigned char *image,size_t size,int caches_disabled,uint32_t address,unsigned bytes,uint32_t value, + esk_esp32_flashspi_command_fn command,void *ctx,int device) { unsigned offset;es_status st; if(!s) return ES_EINVAL; @@ -54,7 +72,7 @@ es_status esk_esp32_flashspi_write(esk_esp32_flashspi *s,const unsigned char *im switch(offset) { case 0: if(value&UINT32_C(0xfffb0000)) return ES_EUNSUPPORTED; - return (value&0x40000u) ? transfer(s,image,size,caches_disabled) : ES_OK; + return (value&0x40000u) ? (device ? device_transfer(s,command,ctx,caches_disabled) : transfer(s,image,size,caches_disabled)) : ES_OK; case 4:s->addr=value;break; case 8: if((value&UINT32_C(0x7f0dc00))!=0x208400u) return ES_EUNSUPPORTED; @@ -62,8 +80,8 @@ es_status esk_esp32_flashspi_write(esk_esp32_flashspi *s,const unsigned char *im case 0x18:if(value!=0x80003043u) return ES_EUNSUPPORTED;break; case 0x1c: /* Preserve unqualified timing/slave/byte-order fields at reset values. */ - if((value&UINT32_C(0x0efffcf1))!=0x40u) return ES_EUNSUPPORTED; - s->user=(value&UINT32_C(0xf1000000))|0x40u;break; + if((value&(device ? UINT32_C(0x04fffcf1) : UINT32_C(0x0efffcf1)))!=0x40u) return ES_EUNSUPPORTED; + s->user=(value&(device ? UINT32_C(0xfb000000) : UINT32_C(0xf1000000)))|0x40u;break; case 0x20: /* SDK and register comments disagree on address-length writability. * Only unchanged 24-bit configuration is qualified in this profile. */ @@ -80,4 +98,8 @@ es_status esk_esp32_flashspi_write(esk_esp32_flashspi *s,const unsigned char *im } return ES_OK; } +es_status esk_esp32_flashspi_write(esk_esp32_flashspi *s,const unsigned char *image,size_t size,int caches_disabled,uint32_t address,unsigned bytes,uint32_t value) +{ return write_impl(s,image,size,caches_disabled,address,bytes,value,NULL,NULL,0); } +es_status esk_esp32_flashspi_device_write(esk_esp32_flashspi *s,esk_esp32_flashspi_command_fn command,void *ctx,int caches_disabled,uint32_t address,unsigned bytes,uint32_t value) +{ if(!command) return ES_EINVAL;return write_impl(s,NULL,0,caches_disabled,address,bytes,value,command,ctx,1); } int esk_esp32_flashspi_irq(const esk_esp32_flashspi *s) { return s && (s->slave&0x210u)==0x210u; } diff --git a/core/mcu/esp32/flashspi.h b/core/mcu/esp32/flashspi.h index f9198af69..5b898a9dc 100644 --- a/core/mcu/esp32/flashspi.h +++ b/core/mcu/esp32/flashspi.h @@ -3,9 +3,11 @@ #ifndef ESK_ESP32_FLASHSPI_H #define ESK_ESP32_FLASHSPI_H #include "mcu/xtensa/execute.h" -/* Opt-in SPI1 single-line USER 03h read profile, CS0, 24-bit address, +/* Default opt-in SPI1 single-line USER 03h read profile, CS0, 24-bit address, * byte-sized data in the W0..15 buffer, both caches disabled. The attachment * is explicitly provided immutable flash storage, not an identified chip. + * The optional device callback below expands command/MOSI/MISO phases for an + * explicitly attached chip; default read-only behavior remains unchanged. * Functional synchronous completion, no pin waveforms, arbiter/clock timing, * DMA, dedicated commands, fast/dual/quad, program/erase/status/ID/security, * global peripheral reset or power model. Unsupported modes fail unchanged. @@ -14,6 +16,14 @@ typedef struct esk_esp32_flashspi { uint32_t addr,ctrl,user,user1,user2,mosi,miso,slave,buffer[16]; } esk_esp32_flashspi; +/* Optional command backend. It receives validated byte-sized single-line + * USER phases and completes synchronously. On failure it must leave state and + * rx bytes unchanged. No re-entry. Transfer, not device programming, completes + * at callback return; the chip may retain an independently timed BUSY state. */ +typedef es_status (*esk_esp32_flashspi_command_fn)(void *ctx,unsigned command,int has_address,uint32_t address, + const unsigned char *tx,size_t tx_len,unsigned char *rx,size_t rx_len); +es_status esk_esp32_flashspi_device_write(esk_esp32_flashspi *s,esk_esp32_flashspi_command_fn command,void *ctx,int caches_disabled, + uint32_t address,unsigned bytes,uint32_t value); void esk_esp32_flashspi_init(esk_esp32_flashspi *s); es_status esk_esp32_flashspi_read(const esk_esp32_flashspi *s,uint32_t address,unsigned bytes,uint32_t *value); es_status esk_esp32_flashspi_write(esk_esp32_flashspi *s,const unsigned char *image,size_t size,int caches_disabled,uint32_t address,unsigned bytes,uint32_t value); diff --git a/core/mcu/esp32/w25q32.c b/core/mcu/esp32/w25q32.c new file mode 100644 index 000000000..123b494da --- /dev/null +++ b/core/mcu/esp32/w25q32.c @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* Winbond W25Q32JV Rev.G (2018-03-27), sections 7/8 and AC timing table. + * Original protocol/state implementation, not vendor implementation code. */ +#include "mcu/esp32/w25q32.h" +#include +void esk_esp32_w25q32_init(esk_esp32_w25q32 *f) { if(f) memset(f,0,sizeof *f); } +es_status esk_esp32_w25q32_command(esk_esp32_w25q32 *f,unsigned char *image,size_t size, + unsigned command,int has_address,uint32_t address,const unsigned char *tx,size_t tx_len,unsigned char *rx,size_t rx_len) +{ + uint64_t duration=0;uint32_t length=0,base=address;size_t i; + if(!f || !image || size!=0x400000u || (tx_len && !tx) || (rx_len && !rx)) return ES_EINVAL; + /* Validate shape before any storage, WEL or pending-operation change. */ + switch(command) { + case 3: + if(!has_address || tx_len || !rx_len || address>=size || rx_len>size-address) return ES_EUNSUPPORTED; + break; + case 5: + if(has_address || tx_len || !rx_len) return ES_EUNSUPPORTED; + break; + case 0x9f: + if(has_address || tx_len || rx_len!=3) return ES_EUNSUPPORTED; + break; + case 6:case 4: + if(has_address || tx_len || rx_len) return ES_EUNSUPPORTED; + break; + case 2: + if(!has_address || !tx_len || tx_len>256 || rx_len || address>=size) return ES_EUNSUPPORTED; + length=(uint32_t)tx_len;duration=3000;break; + case 0x20:case 0x52:case 0xd8: + if(!has_address || tx_len || rx_len || address>=size) return ES_EUNSUPPORTED; + length=command==0x20 ? 4096u : command==0x52 ? 32768u : 65536u; + base=address&~(length-1u);duration=command==0x20 ? 400000u : command==0x52 ? 1600000u : 2000000u;break; + case 0xc7:case 0x60: + if(has_address || tx_len || rx_len) return ES_EUNSUPPORTED; + base=0;length=0x400000;duration=50000000;break; + default:return ES_EUNSUPPORTED; + } + if(command==5) { memset(rx,(f->remaining_us ? 1 : 0)|(f->wel ? 2 : 0),rx_len);return ES_OK; } + if(f->remaining_us) { + /* Busy ignores further non-status instructions. Undefined receive-line + * values are not represented by invented pulled-up or retained bytes. */ + return rx_len ? ES_EUNSUPPORTED : ES_OK; + } + if(command==3) { memcpy(rx,image+address,rx_len);return ES_OK; } + if(command==0x9f) { rx[0]=0xef;rx[1]=0x40;rx[2]=0x16;return ES_OK; } + if(command==6) { f->wel=1;return ES_OK; } + if(command==4) { f->wel=0;return ES_OK; } + if(!f->wel) return ES_OK; /* Hardware ignores writes without WEL. */ + if(command==2) for(i=0;idata[i]=tx[i]; + f->operation=command;f->address=base;f->length=length;f->remaining_us=duration; + return ES_OK; +} +es_status esk_esp32_w25q32_advance(esk_esp32_w25q32 *f,unsigned char *image,size_t size,uint64_t microseconds) +{ + uint32_t i; + if(!f || !image || size!=0x400000u) return ES_EINVAL; + if(!f->remaining_us) return ES_OK; + if(microsecondsremaining_us) { f->remaining_us-=microseconds;return ES_OK; } + if(f->operation==2) { + uint32_t page=f->address&~UINT32_C(255),offset=f->address&255u; + for(i=0;ilength;i++) { + uint32_t at=page+((offset+i)&255u);image[at]=(unsigned char)(image[at]&f->data[i]); + } + } else memset(image+f->address,255,f->length); + f->remaining_us=0;f->wel=0;f->operation=0;f->address=f->length=0; + return ES_OK; +} diff --git a/core/mcu/esp32/w25q32.h b/core/mcu/esp32/w25q32.h new file mode 100644 index 000000000..c4f45a115 --- /dev/null +++ b/core/mcu/esp32/w25q32.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_ESP32_W25Q32_H +#define ESK_ESP32_W25Q32_H +#include "embsim/types.h" +/* Explicit W25Q32JV-IQ unprotected 4MiB storage fixture, NOT identified board + * hardware. Qualified standard-SPI subset: 03/05/06/04/9f/02/20/52/d8/c7/60. + * PP accepts 1..256 bytes; longer frames and array-end read/address aliases + * are unqualified and rejected. Timed PP/erase use Rev.G datasheet MAXIMUM durations and supplied microseconds, + * never instruction counts. Storage commits at completion; intermediate cells, + * power interruption, endurance/disturb, SPI edges, fast/dual/quad, protection + * writes, suspend/resume, status2/3, security/OTP, reset/power and SFDP omitted. + * SR1 upper bits are explicitly fixture-configured unprotected zero. WEL/BUSY + * follow the command lifecycle. Ignored busy reads have unqualified electrical + * output, so fail explicitly instead of fabricating bytes. Caller owns storage + * and serializes all calls; initialize before use. No actual-board BOM claim. */ +typedef struct esk_esp32_w25q32 { + uint64_t remaining_us; + uint32_t wel,operation,address,length; + unsigned char data[256]; +} esk_esp32_w25q32; +void esk_esp32_w25q32_init(esk_esp32_w25q32 *f); +es_status esk_esp32_w25q32_command(esk_esp32_w25q32 *f,unsigned char *image,size_t size, + unsigned command,int has_address,uint32_t address,const unsigned char *tx,size_t tx_len,unsigned char *rx,size_t rx_len); +/* Returns ES_OK; commits at the deadline, keeping memory private until then. + * Excess time is discarded; one operation can be outstanding. */ +es_status esk_esp32_w25q32_advance(esk_esp32_w25q32 *f,unsigned char *image,size_t size,uint64_t microseconds); +#endif diff --git a/docs/STATUS.md b/docs/STATUS.md index bddae1250..e8d26ae71 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -98,8 +98,9 @@ explicit flush/enable sequencing. Evidence: 7,289 native assertions, 488 parsed vendor-fact translations, 256 compiled mapped workers and 64 exact replays. Compiled PRO setup plus original test-ROM handoff also passes 128 reset cycles into a mapped worker. Immutable backing and disabled-cache table edits bound this -profile; SPI programming, real cache tags/timing, live SDK mmap/invalidation, -PSRAM/high PIDs and actual ROM/SDK/board boot remain unimplemented. +profile; the default attachment omits SPI programming. Real cache tags/timing, +live SDK mmap/invalidation, PSRAM/high PIDs and actual ROM/SDK/board boot remain +unimplemented. The separate named-part fixture below adds bounded programming. The [CPU task-context register profile](engineering/esp32-context-registers.md) now implements unprivileged THREADPTR, privileged MISC0..3 and read-only PRID with an explicitly supplied fixture constant. PRID's full silicon value is still @@ -120,8 +121,18 @@ require both caches disabled. Evidence: 37,466 native assertions, 27 parsed regi reference comparisons and 128 actual ESP32 C reads pass; 32 of these also load and execute a generated RAM worker (32 exact replays, 91,568 retired instructions). Seven unchanged SDK helper bodies are tested, not the full SDK initialization or -boot stack. Flash program/erase, DMA, SPI timing, GPIO/pin mux and actual ROM/SDK -boot remain unsupported. +boot stack. The default attachment omits program/erase. DMA, SPI timing, GPIO/pin +mux and actual ROM/SDK boot remain unsupported; the named-part option below +extends the storage commands. +An explicit [W25Q32JV-IQ programming profile](engineering/esp32-flash-programming.md) +now adds write-enable/BUSY, page programming/wrap and aligned erase through SPI1. +It requires an explicitly selected unprotected 4 MiB part fixture; the requested +board's actual flash chip remains unverified. External microseconds drive +conservative datasheet-maximum durations. Native tests and 2,631 independent +command/time comparisons pass; compiled firmware programs and executes 32 RAM +workers (32 exact replays, 125,856 retired instructions). Fresh post-completion +flush is required before cache enable. Full chip/protection/electrical behavior, +physical timing and real ROM/SDK boot remain unqualified. No runnable ESP32 target is added, and the existing RISC-V ESP32-C3 is not a substitute. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 3d17e3e85..411625aab 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -815,8 +815,21 @@ are functional; unsupported modes/commands and missing extents fail unchanged. Evidence includes 37,466 native assertions, 27 parsed register defaults/384 reference cases and 128 actual ESP32 C reads using seven unchanged SDK helper bodies. Thirty-two runs load and call a generated RAM worker; 32 exact replays retire 91,568 -instructions. Physical flash identity/program/erase, SPI timing, DMA, pin mux, -full SDK initialization and actual ROM/SDK/board boot remain unqualified. +instructions. Physical flash identity, SPI timing, DMA, pin mux, full SDK +initialization and actual ROM/SDK/board boot remain unqualified. The separate +named-part fixture below adds bounded program/erase behavior. + +## Implemented: explicit W25Q32JV programming fixture + +The [named flash-part contract](esp32-flash-programming.md) adds an explicit +unprotected W25Q32JV-IQ 4 MiB profile. It does not identify the requested board's +actual flash part. Standard write-enable/status, page program/wrap and aligned +erase use supplied external microseconds with conservative datasheet-maximum +durations. SPI1 USER transmit/receive and cache re-enable boundaries are covered. +Native tests, 2,631 independent command/time comparisons and actual compiled +firmware pass: 32 programmed RAM workers, 32 exact replays and 125,856 retired +instructions. Full chip/protection/power/electrical behavior, physical timing, +complete SPI controller behavior and real ROM/SDK/board boot remain unqualified. ## Establish identity before catalog publication diff --git a/docs/engineering/esp32-flash-programming.md b/docs/engineering/esp32-flash-programming.md new file mode 100644 index 000000000..d0f2d83df --- /dev/null +++ b/docs/engineering/esp32-flash-programming.md @@ -0,0 +1,144 @@ +# ESP32 explicit W25Q32JV programming profile + +**Implemented: a bounded, explicitly selected W25Q32JV-IQ unprotected 4 MiB +standard-SPI profile, connected to experimental SPI1. This does not identify the +requested board's actual flash chip or add real ROM/ESP-IDF boot.** + +## Real operation + +NOR programming clears bits; erase restores the selected region to ones. Page +Program needs Write Enable and wraps within a 256-byte page. Erase aligns to the +selected sector/block. The chip runs its own self-timed operation after the SPI +transaction completes. Firmware polls status BUSY, and Write Enable is cleared +when the program/erase finishes. A controller's transaction completion and the +flash chip's programming completion are separate events. + +The model implements this behavior from Winbond W25Q32JV Revision G, March 27, +2018. IQ/JQ identifies as EF 40 16; IM/JM has a different type byte and is not +silently substituted. The requested TECNOIOT board's flash marking/BOM remain +unverified. Selecting this part is an explicit test configuration, not a board +identity conclusion. + +## Qualified subset + +`w25q32.c` is an original protocol/state implementation with no ESP32 register +addresses. The caller owns its 4 MiB storage. `flashspi.c` supplies the separate +controller/register bridge. Supported standard-SPI commands: + +| Command | Behavior | +|---|---| +| 03h Read Data | Sequential bytes within the provided array; idle only | +| 05h Read Status-1 | Repeated BUSY bit 0 / WEL bit 1; upper bits fixed to the explicit unprotected fixture configuration | +| 06h / 04h | Set/clear Write Enable when idle; ignored during BUSY | +| 9Fh | Exactly three IQ-profile JEDEC ID bytes, idle only | +| 02h | 1..256-byte Page Program, page wrapping, bitwise one-to-zero programming | +| 20h / 52h / D8h | Aligned 4 KiB / 32 KiB / 64 KiB erase | +| C7h / 60h | Whole-array erase | + +Writes without WEL complete their SPI transaction but leave the array and chip +state unchanged. Accepted program/erase sets BUSY; supported no-receive commands +are ignored while BUSY. Status reads remain available. An ignored data/ID read +has unqualified electrical output; the model fails it explicitly instead of +inventing a pulled-up byte. Unsupported command shapes, receive extents or +operations also fail without changing output bytes or device state. + +All 256-byte page offsets, wrap and repeated bit-clearing behavior are tested. +This functional bit model does not qualify manufacturer limits on repeated +programming, endurance, disturb, retention, partial power failure or damage. +Longer-than-256-byte program frames, array-end read wrapping/address aliases, +fast/dual/quad/QPI, SFDP, status2/3, protection/status writes, security/OTP, +suspend/resume, chip reset/power-down, startup timing, supply thresholds and +physical SPI/WP/HOLD waveforms remain unimplemented. Protection writes are +rejected rather than accepted without protecting memory. This is not a complete +W25Q32JV component model. + +## Time and storage + +The deterministic timing fixture uses the datasheet's **maximum** durations: + +| Operation | Supplied time to completion | +|---|---| +| Page Program | 3,000 us | +| 4 KiB erase | 400,000 us | +| 32 KiB erase | 1,600,000 us | +| 64 KiB erase | 2,000,000 us | +| Chip erase | 50,000,000 us | + +These are conservative fixture durations, not measured hardware timing or a +claim that every physical operation takes the maximum. Typical timing, voltage/ +temperature variation and hardware calibration are not qualified. External time +is advanced with `esk_esp32_fabric_flash_advance`; `flash_deadline` reports the +remaining microseconds, with zero meaning idle. No instruction count, wall clock +or APP CPU clock gate implicitly advances or stops the chip's clock. + +Program data is latched when accepted. The model commits array changes at +completion; intermediate cell states/power interruption are unqualified. Normal +reads cannot observe the intermediate array, and status stays BUSY until the +supplied deadline. Excess advance time is discarded after the single pending +operation completes; arithmetic does not wrap for a maximal host advance. + +## ESP32 integration + +Install exactly 4 MiB of backing, enable the SPI1 attachment, then explicitly +call `esk_esp32_fabric_w25q32_configure`. A different backing extent is rejected. +The default immutable attachment retains its previous behavior. Named-part +attachment/detachment requires both caches disabled and an idle chip. Changing +attachment resets chip fixture status, not the array or SPI controller state; +this host setup action is not a silicon power/reset claim. Image replacement is +rejected while the named part is attached. CPU-only APP reset preserves the +shared chip state, pending operation, data and independent time. + +The selected profile expands SPI1 USER command, address, MOSI and MISO phases. +Its data buffer limits each transaction to 64 bytes, or 32 bytes in the selected +high buffer. Firmware must split programming at both page and controller-buffer +boundaries. Both caches must be disabled for every SPI1 transaction because the +SPI0/1 arbiter is still unqualified. Dedicated hardware flash commands and DMA +remain unsupported. Physical peripheral clocking/pin mux are not added. + +The SPI1 completion flag/source 29 reports transaction completion, including +ignored writes without WEL. It does not become a separate chip-completion IRQ. +New programming invalidates the functional cache profile. Re-enable is rejected +while BUSY, even if firmware flushes early. Completion marks both caches dirty +again: a fresh clear/set flush after completion is required before enabling a +mapped view of the modified bytes. This is a conservative coherence contract, +not a hardware cache-tag or live SDK invalidation implementation. + +## Evidence and reproduction + +`test_esp32_w25q32` passes 1,205,963 native assertions, covering every page offset, +lengths through a full page, latches, ignored commands, exact deadline boundaries, +erase extents, JEDEC ID, unchanged failures, high transmit/receive buffers, +CPU-only reset preservation and post-program cache flush requirements. + +`run_w25q32.py` hash-checks the external datasheet, parses its maximum durations, +command codes and IQ identity, and compares against an independent Python array +and command/time oracle: 2,631 comparisons, 219 completed operations and 37 +writes ignored without WEL. Full array contents are checked before and after +completion. Neither vendor implementation nor datasheet contents are vendored. + +`run_spi_c.py --program-workers` uses the actual ESP32 compiler and the same +seven unchanged SDK helper bodies as the [read profile](esp32-spi1-reads.md). +Compiled firmware holds a shared-controller atomic lock, enables writes, splits +at page boundaries, programs supplied code, polls BUSY, reads it back and copies +it to instruction RAM before calling it. Evidence: 128 reads, 32 programmed RAM +workers, 32 exact replays, 125,856 retired instructions and 6,144 scheduled turns +with the chip busy. The external test fixture supplies 1 ms time events every +37 scheduled turns; this artificial schedule is not a processor clock model. +There are no Python memory callbacks or host worker-PC shortcuts. The supplied +RAM payload is a generated test input, not a ROM/bootloader or serial programmer. + +Primary reference: [Winbond W25Q32JV Revision G](https://www.winbond.com/resource-files/w25q32jv%20revg%2003272018%20plus.pdf), +inspected 2026-11-05, sections 7/8 and AC characteristics. PDF SHA-256: +`b2585095ac503e7cee7916ab3f025a51699b49e2cad7f6782e92b828c1cc09d8`. +Use the pinned Espressif tools and SDK headers in the read-profile contract. + +```sh +python3 tests/vendor/xtensa_integer/run_w25q32.py \ + --datasheet /path/to/w25q32jv-revg.pdf +python3 tests/vendor/xtensa_integer/run_spi_c.py --program-workers \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld \ + --nm /path/to/xtensa-esp32-elf-nm \ + --ll-header /path/to/spi_flash_ll.h --struct-header /path/to/spi_struct.h +``` diff --git a/docs/engineering/esp32-spi1-reads.md b/docs/engineering/esp32-spi1-reads.md index a2490aac3..2764cb8b9 100644 --- a/docs/engineering/esp32-spi1-reads.md +++ b/docs/engineering/esp32-spi1-reads.md @@ -107,6 +107,14 @@ helper execution is not the whole SDK initialization/common-command stack, an actual Espressif ROM, full interrupt-driven driver or bootloader acceptance. Retired instructions are not physical cycles. No hardware capture was collected. +## Optional named programming attachment + +The default read-only profile above keeps its existing contract. An explicitly +selected [W25Q32JV-IQ fixture](esp32-flash-programming.md) adds a bounded named-chip +command backend, transmit phases and separately advanced programming time. Its +scope and omissions are separate; this does not retroactively qualify arbitrary +chips, the board BOM, full SDK initialization or physical SPI timing. + ## References and reproduction Inspected 2026-11-05: ESP32 TRM v5.9 chapter 20, and these external ESP-IDF v4.4.7 diff --git a/tests/unit/test_esp32_w25q32.c b/tests/unit/test_esp32_w25q32.c new file mode 100644 index 000000000..d358df13a --- /dev/null +++ b/tests/unit/test_esp32_w25q32.c @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/w25q32.h" +#include "mcu/esp32/machine.h" +static es_status command(esk_esp32_w25q32 *f,unsigned char *image,unsigned cmd,int addr,uint32_t address,const unsigned char *tx,size_t n,unsigned char *rx,size_t m) +{ return esk_esp32_w25q32_command(f,image,0x400000,cmd,addr,address,tx,n,rx,m); } +static void page_program_and_latches(void) +{ + unsigned char *image=(unsigned char *)malloc(0x400000);unsigned char tx[256],rx[3];unsigned offset,k,i; + const unsigned lengths[]={1,2,16,64,255,256};esk_esp32_w25q32 f; + CHECK(image!=NULL);if(!image) return;memset(image,255,0x400000); + for(offset=0;offset<256;offset++) for(k=0;kwrite(bus->ctx,0x3ff4201c,4,user),ES_OK); + CHECK_EQ(bus->write(bus->ctx,0x3ff42024,4,0x70000000u|cmd),ES_OK); + CHECK_EQ(bus->write(bus->ctx,0x3ff42004,4,(at<<8)|255u),ES_OK); + CHECK_EQ(bus->write(bus->ctx,0x3ff42028,4,tx_bytes ? tx_bytes*8u-1u : 0u),ES_OK); + CHECK_EQ(bus->write(bus->ctx,0x3ff4202c,4,rx_bytes ? rx_bytes*8u-1u : 0u),ES_OK); + CHECK_EQ(bus->write(bus->ctx,0x3ff42000,4,0x40000),ES_OK); +} +static void flash_time_reset_and_cache_integration(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_esp32_fabric *f;esk_xtensa_memory bus;uint32_t value=0;uint64_t left=77; + unsigned char *image=(unsigned char *)malloc(0x400000);CHECK(image!=NULL);if(!image) return;memset(image,255,0x400000); + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK);f=esk_esp32_machine_fabric(m); + CHECK_EQ(esk_esp32_fabric_flash_deadline(f,&left),ES_EUNSUPPORTED);CHECK_EQ(left,77); + CHECK_EQ(esk_esp32_fabric_w25q32_configure(f,1),ES_EUNSUPPORTED); + CHECK_EQ(esk_esp32_fabric_flash(f,image,65536),ES_OK); + CHECK_EQ(esk_esp32_fabric_w25q32_configure(f,1),ES_EUNSUPPORTED); + CHECK_EQ(esk_esp32_fabric_flash(f,image,0x400000),ES_OK);CHECK_EQ(esk_esp32_fabric_w25q32_configure(f,1),ES_OK); + CHECK_EQ(esk_esp32_fabric_spi1_configure(f,1),ES_OK);bus=esk_esp32_machine_bus(m,0); + CHECK_EQ(bus.write(bus.ctx,0x3ff42008,4,0x208400),ES_OK); + spi_command(&bus,6,0,0,0,0);CHECK_EQ(bus.write(bus.ctx,0x3ff42080,4,0x12345678),ES_OK); + spi_command(&bus,2,1,0x100,4,0);CHECK_EQ(esk_esp32_fabric_flash_deadline(f,&left),ES_OK);CHECK_EQ(left,3000); + spi_command(&bus,5,0,0,0,1);CHECK_EQ(bus.read(bus.ctx,0x3ff42080,4,&value),ES_OK);CHECK_EQ(value&255u,3); + CHECK_EQ(esk_esp32_fabric_w25q32_configure(f,0),ES_EUNSUPPORTED); + CHECK_EQ(esk_esp32_fabric_flash(f,image,0x400000),ES_EUNSUPPORTED); + CHECK_EQ(bus.write(bus.ctx,0x3ff10000,4,0),ES_OK);CHECK_EQ(bus.write(bus.ctx,0x3ff00044,4,0x8ef),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff00040,4,0),ES_OK);CHECK_EQ(bus.write(bus.ctx,0x3ff00040,4,16),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff00040,4,24),ES_EUNSUPPORTED); /* Flush while busy is insufficient. */ + CHECK_EQ(esk_esp32_fabric_flash_advance(f,2999),ES_OK);CHECK_EQ(esk_esp32_machine_app_reset_configure(m,1),ES_OK); + CHECK_EQ(esk_esp32_fabric_flash_deadline(f,&left),ES_OK);CHECK_EQ(left,1); + CHECK_EQ(esk_esp32_fabric_flash_advance(f,1),ES_OK);CHECK_EQ(esk_esp32_fabric_flash_deadline(f,&left),ES_OK);CHECK_EQ(left,0); + CHECK_EQ(bus.write(bus.ctx,0x3ff00040,4,24),ES_EUNSUPPORTED); /* Needs fresh post-completion flush. */ + spi_command(&bus,5,0,0,0,1);CHECK_EQ(bus.read(bus.ctx,0x3ff42080,4,&value),ES_OK);CHECK_EQ(value&255u,0); + spi_command(&bus,3,1,0x100,0,4);CHECK_EQ(bus.read(bus.ctx,0x3ff42080,4,&value),ES_OK);CHECK_EQ(value,0x12345678); + CHECK_EQ(bus.write(bus.ctx,0x3ff00040,4,0),ES_OK);CHECK_EQ(bus.write(bus.ctx,0x3ff00040,4,16),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff00040,4,24),ES_OK);CHECK_EQ(bus.read(bus.ctx,0x3f400100,4,&value),ES_OK);CHECK_EQ(value,0x12345678); + CHECK_EQ(bus.write(bus.ctx,0x3ff00040,4,16),ES_OK); + /* High MOSI and high MISO buffers use separate byte lanes. */ + spi_command(&bus,6,0,0,0,0); + CHECK_EQ(bus.write(bus.ctx,0x3ff420a0,4,0x1234cdab),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff4201c,4,0xca000040),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff42024,4,0x70000002),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff42004,4,0x2ffff),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff42028,4,15),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff42000,4,0x40000),ES_OK); + CHECK_EQ(esk_esp32_fabric_flash_advance(f,3000),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff420a0,4,0),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff4201c,4,0xd1000040),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff42024,4,0x70000003),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff4202c,4,7),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff42000,4,0x40000),ES_OK); + CHECK_EQ(bus.read(bus.ctx,0x3ff420a0,4,&value),ES_OK);CHECK_EQ(value,0xab); + CHECK_EQ(bus.read(bus.ctx,0x3ff42080,4,&value),ES_OK);CHECK_EQ(value,0x12345678); + spi_command(&bus,3,1,0x200,0,1);CHECK_EQ(bus.read(bus.ctx,0x3ff42080,4,&value),ES_OK);CHECK_EQ(value&255u,0xcd); + CHECK_EQ(esk_esp32_fabric_w25q32_configure(f,0),ES_OK); + left=77;CHECK_EQ(esk_esp32_fabric_flash_deadline(f,&left),ES_EUNSUPPORTED);CHECK_EQ(left,77); + esk_esp32_machine_destroy(m);free(image);CHECK_EQ(a.live_bytes,0); +} +TEST_MAIN_BEGIN + (void)t_str_eq;RUN(page_program_and_latches);RUN(erase_sizes_and_ignored_commands);RUN(flash_time_reset_and_cache_integration); +TEST_MAIN_END diff --git a/tests/vendor/README.md b/tests/vendor/README.md index 9ef9e58cc..a5a7e03b1 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -172,3 +172,13 @@ unqualified. Reproduction is in the context register contract above. replays retire 91,568 instructions. This is an opt-in single-line 03h read profile, not flash programming, DMA, physical timing or actual ROM/SDK boot. See the [SPI1 read contract and reproduction](../../docs/engineering/esp32-spi1-reads.md). + +`xtensa_integer/run_w25q32.py --datasheet` hash-checks the external Winbond +W25Q32JV Rev.G PDF (needs `pdftotext`) and parses timing maxima and IQ ID: +2,631 command/time comparisons, 219 completed operations, 37 ignored writes. +`run_spi_c.py --program-workers` adds actual compiled write-enable/program/status +polling before reading back and calling generated code: 32 workers, 32 exact +replays, 125,856 retired instructions. Time is an explicit external fixture, +not derived from instruction counts. This named unprotected chip profile is not +a board BOM, full chip or ROM/SDK qualification. See +[programming scope and reproduction](../../docs/engineering/esp32-flash-programming.md). diff --git a/tests/vendor/xtensa_integer/run_controls.py b/tests/vendor/xtensa_integer/run_controls.py index 5f8f3bf4d..3cf5a5b44 100644 --- a/tests/vendor/xtensa_integer/run_controls.py +++ b/tests/vendor/xtensa_integer/run_controls.py @@ -38,7 +38,7 @@ def main(): with tempfile.TemporaryDirectory(prefix='embsim-controls-') as tmp: lib = Path(tmp) / 'controls.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_flash_c.py b/tests/vendor/xtensa_integer/run_flash_c.py index 384575a75..cedf1e1dc 100644 --- a/tests/vendor/xtensa_integer/run_flash_c.py +++ b/tests/vendor/xtensa_integer/run_flash_c.py @@ -68,7 +68,7 @@ def compile_code(name, source, base, entry, flags=()): flash_image = bytes(image) lib = out / 'flash.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_flashmap.py b/tests/vendor/xtensa_integer/run_flashmap.py index df947b7a1..915cb03fb 100644 --- a/tests/vendor/xtensa_integer/run_flashmap.py +++ b/tests/vendor/xtensa_integer/run_flashmap.py @@ -51,7 +51,7 @@ def initial(name): with tempfile.TemporaryDirectory(prefix='embsim-flashmap-') as tmp: lib = Path(tmp) / 'flashmap.so' subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', - '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), str(ROOT / 'core/mcu/esp32/flashmap.c'), str(ROOT / 'core/mcu/esp32/flashspi.c'), + '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), str(ROOT / 'core/mcu/esp32/flashmap.c'), str(ROOT / 'core/mcu/esp32/flashspi.c'), str(ROOT / 'core/mcu/esp32/w25q32.c'), str(ROOT / 'core/util/alloc.c'), '-o', str(lib)], check=True) api = C.CDLL(str(lib)); pointer = C.c_void_p def bind(name, result, *params): diff --git a/tests/vendor/xtensa_integer/run_ipi_c.py b/tests/vendor/xtensa_integer/run_ipi_c.py index 2db306e63..5b979b0d8 100644 --- a/tests/vendor/xtensa_integer/run_ipi_c.py +++ b/tests/vendor/xtensa_integer/run_ipi_c.py @@ -92,7 +92,7 @@ def main(): if len(f := line.split()) == 3} lib = out / 'ipi.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_machine_c.py b/tests/vendor/xtensa_integer/run_machine_c.py index 2afefa685..e21bd38f1 100644 --- a/tests/vendor/xtensa_integer/run_machine_c.py +++ b/tests/vendor/xtensa_integer/run_machine_c.py @@ -59,7 +59,7 @@ def main(): if len(f := line.split()) == 3} lib = out / 'machine.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), diff --git a/tests/vendor/xtensa_integer/run_reset_c.py b/tests/vendor/xtensa_integer/run_reset_c.py index b21c44392..90eca177d 100644 --- a/tests/vendor/xtensa_integer/run_reset_c.py +++ b/tests/vendor/xtensa_integer/run_reset_c.py @@ -117,7 +117,7 @@ def compile_image(name, source, base, entry, assembly=False): rom = rom + bytes(0x60000 - len(rom)) # original test fixture, not a product ROM lib = out / 'reset.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_spi_c.py b/tests/vendor/xtensa_integer/run_spi_c.py index b4cf9dc73..19975fefe 100644 --- a/tests/vendor/xtensa_integer/run_spi_c.py +++ b/tests/vendor/xtensa_integer/run_spi_c.py @@ -5,7 +5,8 @@ Both cores serialize the shared controller with a compiled atomic lock. Some runs copy code from SPI into instruction RAM and call it. No vendor ROM/SDK boot, -DMA, flash programming, physical timing or complete board qualification. +DMA, physical timing or complete board qualification. --program-workers uses +the explicitly selected unprotected W25Q32JV-IQ subset, not a board identity. """ import argparse import ctypes as C @@ -50,6 +51,40 @@ return entry ? ((unsigned (*)(unsigned))entry)(value) : checksum; } ''' +PROGRAM_PREFIX = r""" + if(entry) { + unsigned sent=0; + while(sentlength-sent) n=length-sent; + if(n>64) n=64; + dev->ctrl.fastrd_mode=0; + dev->user.val=0x80000040; + spi_flash_ll_set_command(dev,6,8); + spi_flash_ll_user_start(dev); + while(!spi_flash_ll_cmd_is_done(dev)) { } + dev->user.val=0xc8000040; + spi_flash_ll_set_command(dev,2,8); + spi_flash_ll_set_addr_bitlen(dev,24); + spi_flash_ll_set_usr_address(dev,address+sent,24); + spi_flash_ll_set_mosi_bitlen(dev,n*8); + spi_flash_ll_set_miso_bitlen(dev,0); + for(unsigned i=0;idata_buf[i]=((unsigned *)(out+sent))[i]; + spi_flash_ll_user_start(dev); + while(!spi_flash_ll_cmd_is_done(dev)) { } + dev->user.val=0x90000040; + spi_flash_ll_set_command(dev,5,8); + spi_flash_ll_set_miso_bitlen(dev,8); + spi_flash_ll_set_mosi_bitlen(dev,0); + do { + spi_flash_ll_user_start(dev); + while(!spi_flash_ll_cmd_is_done(dev)) { } + } while(dev->data_buf[0]&1); + sent+=n; + } + } +""" + WORKER = 'unsigned worker(unsigned x) { return (x ^ TAG) + (x >> 7); }\n' @@ -57,6 +92,7 @@ def main(): ap=argparse.ArgumentParser(description=__doc__) for name in ['compiler','objcopy','linker','nm']: ap.add_argument('--'+name,required=True) ap.add_argument('--ll-header',type=Path,required=True);ap.add_argument('--struct-header',type=Path,required=True) + ap.add_argument('--program-workers',action='store_true') ap.add_argument('--cc',default='cc');args=ap.parse_args();rng=random.Random(0x5b11) ll=read_header(args.ll_header,'d1331e0d99ff2377e80e7ec4b8f314453345e68d894f5d2b3139c0e8069ae724') struct=read_header(args.struct_header,'97fffd958e992ea25af94e08bb6e5b6c7487671b3e070efa68e80d216db383b2') @@ -70,7 +106,7 @@ def main(): end+=1 helpers.append(ll[match.start():end]) notice=ll[:ll.index('#pragma once')] - total=0;calls=0;loaded=0 + total=0;calls=0;loaded=0;busy_turns=0 with tempfile.TemporaryDirectory(prefix='embsim-spi-c-') as tmp: out=Path(tmp);(out/'vendor-spi-struct.h').write_text(struct,encoding='utf-8') def compile_code(name,source,base,entry,flags=()): @@ -83,17 +119,19 @@ def compile_code(name,source,base,entry,flags=()): symbols={f[2]:int(f[0],16) for line in subprocess.check_output([args.nm,str(elf)],text=True).splitlines() if len(f:=line.split())==3} return binary.read_bytes(),symbols[entry] - source=notice+'\n#include \n#include \n#include "vendor-spi-struct.h"\n'+'\n'.join(helpers)+SOURCE + body=SOURCE.replace(' dev->ctrl.fastrd_mode=0;', PROGRAM_PREFIX+' dev->ctrl.fastrd_mode=0;',1) if args.program_workers else SOURCE + source=notice+'\n#include \n#include \n#include "vendor-spi-struct.h"\n'+'\n'.join(helpers)+body code,start=compile_code('operation',source,0x40080000,'operation') - image=bytearray(rng.getrandbits(8) for _ in range(4*65536));entries=[];lengths=[];tags=[0x12345678,0x98765432] + image=bytearray(rng.getrandbits(8) for _ in range(0x400000 if args.program_workers else 4*65536));entries=[];lengths=[];payloads=[];tags=[0x12345678,0x98765432] for cpu in range(2): worker,entry=compile_code('worker'+str(cpu),WORKER,0x40088000+cpu*4096,'worker',['-DTAG='+hex(tags[cpu])+'u']) worker+=bytes((-len(worker))%4);assert 16>7))&0xffffffff params.append([address,size,0x3ffb8000+cpu*256,entry,value,high]);expected.append((wanted,data)) @@ -128,6 +169,7 @@ def bind(name,result,*params): try: ram=fabric(m);bus=bus_get(m,0) assert flash(ram,C.c_char_p(image),len(image))==0 and spi(ram,1)==0 + if args.program_workers: assert select(ram,1)==0 assert load(ram,0,0x40080000,C.c_char_p(code),len(code))==0 assert bus.write(bus.ctx,0x3ff00030,4,1)==0 states=[] @@ -135,9 +177,17 @@ def bind(name,result,*params): assert configure(m,cpu,start)==0 w=core(m,cpu).contents;w.ps|=1<<16;w.cpu.a[1]=0x3ffd0000+cpu*4096;w.cpu.a[4]=0x7ffffff0 for n,arg in enumerate(params[cpu]):w.cpu.a[6+n]=arg + if args.program_workers and params[cpu][3]: + payload=payloads[cpu];assert load(ram,cpu,params[cpu][2],C.c_char_p(payload),len(payload))==0 states.append(w);assert run(m,cpu,1)==0 done=[False,False];event=C.c_uint();fault=Fault();trace=[];worker_steps=[0,0] for tick in range(30000): + # Explicit external flash-time fixture: 1 ms events every 37 + # scheduled turns. This is not a CPU instruction/cycle timing model. + if args.program_workers: + remaining=C.c_uint64();assert deadline(ram,C.byref(remaining))==0 + busy_turns+=remaining.value!=0 + if tick%37==0: assert advance(ram,1000)==0 cpu=(tick+trial)%2;w=states[cpu] if done[cpu]:continue if w.cpu.pc==0x7ffffff0: @@ -166,7 +216,9 @@ def bind(name,result,*params): else:previous=result finally:destroy(m) assert a.live_bytes==0 - print(f'{calls} actual ESP32 SPI1 C reads, {loaded} SPI-loaded RAM workers, 32 exact replays, {total} retired instructions; seven unchanged SDK helper bodies') + if args.program_workers: assert busy_turns>0 + action='SPI-programmed' if args.program_workers else 'SPI-loaded' + print(f'{calls} actual ESP32 SPI1 C reads, {loaded} {action} RAM workers, 32 exact replays, {total} retired instructions; {busy_turns} busy turns; seven unchanged SDK helper bodies') if __name__=='__main__':main() diff --git a/tests/vendor/xtensa_integer/run_tls_c.py b/tests/vendor/xtensa_integer/run_tls_c.py index 52c681f6f..6a4d1ecf6 100644 --- a/tests/vendor/xtensa_integer/run_tls_c.py +++ b/tests/vendor/xtensa_integer/run_tls_c.py @@ -83,7 +83,7 @@ def main(): assert int.from_bytes(template[symbols['zero']:symbols['zero'] + 4], 'little') == 0 lib = out / 'tls.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_w25q32.py b/tests/vendor/xtensa_integer/run_w25q32.py new file mode 100644 index 000000000..a81a8d300 --- /dev/null +++ b/tests/vendor/xtensa_integer/run_w25q32.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""W25Q32JV protocol oracle and hash-pinned Rev.G maximum-duration facts. + +PDF remains external, requires pdftotext. Does not identify the requested board's +chip or qualify power loss, protection, electrical timing or full command set. +""" +import argparse +import ctypes as C +import hashlib +from pathlib import Path +import random +import re +import subprocess +import tempfile +from run_execute import ROOT + + +class NOR(C.Structure): + _fields_=[('remaining_us',C.c_uint64)]+[(name,C.c_uint32) for name in ['wel','operation','address','length']]+[('data',C.c_ubyte*256)] + + +def main(): + ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('--datasheet',type=Path,required=True) + ap.add_argument('--cc',default='cc');args=ap.parse_args();rng=random.Random(0x2532) + assert hashlib.sha256(args.datasheet.read_bytes()).hexdigest()=='b2585095ac503e7cee7916ab3f025a51699b49e2cad7f6782e92b828c1cc09d8' + text=subprocess.check_output(['pdftotext','-layout',str(args.datasheet),'-'],text=True) + names={2:'tPP',0x20:'tSE',0x52:'tBE1',0xd8:'tBE2',0xc7:'tCE',0x60:'tCE'};times={} + for cmd,symbol in names.items(): + match=re.search(r'\b'+symbol+r'\s+([\d.]+)\s+([\d,]+)\s+(ms|s)\b',text);assert match,symbol + times[cmd]=int(match[2].replace(',',''))*(1000 if match[3]=='ms' else 1000000) + identity=int(re.search(r'W25Q32JV-IQ/JQ\s+15h\s+([0-9A-F]+)h',text)[1],16) + manufacturer=int(re.search(r'Winbond Serial Flash\s+([0-9A-F]+)h',text)[1],16) + jedec=bytes([manufacturer,identity>>8,identity&255]) + for name,cmd in [('Page Program',2),(r'Sector Erase \(4KB\)',0x20),(r'Block Erase \(32KB\)',0x52),(r'Block Erase \(64KB\)',0xd8)]: + assert int(re.search(name+r'\s+([0-9A-F]+)h\s+A23-A16',text)[1],16)==cmd + with tempfile.TemporaryDirectory(prefix='embsim-w25q32-ref-') as tmp: + lib=Path(tmp)/'nor.so' + subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC', + '-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),str(ROOT/'core/mcu/esp32/w25q32.c'),'-o',str(lib)],check=True) + api=C.CDLL(str(lib));p=C.POINTER(NOR) + init=api.esk_esp32_w25q32_init;init.argtypes=[p];init.restype=None + command=api.esk_esp32_w25q32_command + command.argtypes=[p,C.c_void_p,C.c_size_t,C.c_uint,C.c_int,C.c_uint32,C.c_void_p,C.c_size_t,C.c_void_p,C.c_size_t];command.restype=C.c_int + advance=api.esk_esp32_w25q32_advance;advance.argtypes=[p,C.c_void_p,C.c_size_t,C.c_uint64];advance.restype=C.c_int + size=4*1024*1024;expected=bytearray(b'\xff'*size);actual=C.create_string_buffer(bytes(expected),size) + nor=NOR();init(C.byref(nor));wel=False;pending=None;checks=0;completed=0;ignored=0 + def call(cmd,address=None,data=b'',read=0): + nonlocal checks + rx=C.create_string_buffer(b'\xa5'*read,read) if read else None + status=command(C.byref(nor),actual,size,cmd,address is not None,address or 0,C.c_char_p(data) if data else None,len(data),rx,read) + checks+=1;assert status==0,(hex(cmd),status) + return rx.raw if rx else b'' + def tick(amount): + nonlocal pending,wel,completed,checks + assert advance(C.byref(nor),actual,size,amount)==0;checks+=1 + if pending: + cmd,address,data,remaining=pending + if amount>=remaining: + if cmd==2: + page=address-address%256 + for i,byte in enumerate(data):expected[page+(address+i)%256]&=byte + else: + count={0x20:4096,0x52:32768,0xd8:65536,0xc7:size,0x60:size}[cmd] + base=address-address%count;expected[base:base+count]=b'\xff'*count + pending=None;wel=False;completed+=1 + else:pending=cmd,address,data,remaining-amount + assert nor.remaining_us==(pending[3] if pending else 0) and bool(nor.wel)==wel + for trial in range(256): + if trial%23==0:cmd=[0x20,0x52,0xd8,0xc7,0x60][(trial//23)%5] + else:cmd=2 + address=rng.randrange(size);data=rng.randbytes(rng.randint(1,256)) if cmd==2 else b'' + if trial%7: + call(6);wel=True + call(cmd,address if cmd not in [0xc7,0x60] else None,data) + if wel:pending=cmd,address if cmd not in [0xc7,0x60] else 0,data,times[cmd] + else:ignored+=1 + assert nor.remaining_us==(times[cmd] if wel else 0) + assert call(5,read=7)==bytes([3 if wel else 0])*7 + if pending: + snapshot=bytes(nor);call(4);call(6);assert bytes(nor)==snapshot + tick(0);tick(times[cmd]-1) + # All cells still match the previous completed state. + assert actual.raw==expected + tick(1) + else:tick((1<<64)-1) + assert actual.raw==expected + assert call(5,read=1)==b'\x00' + assert call(0x9f,read=3)==jedec + assert call(3,address,data=b'',read=min(64,size-address))==expected[address:address+min(64,size-address)] + assert not nor.remaining_us and not nor.wel + print(f'{checks} independent NOR command/tick comparisons, {completed} completed operations, {ignored} writes ignored without WEL; maximum durations and IQ ID parsed from pinned datasheet') + + +if __name__=='__main__':main() From 14c4c44eb49f9b37773421ff28c32bcfde4aa85c Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 15:00:20 +0000 Subject: [PATCH 24/57] Record actual ESP32 ROM boot blocker and SDK reference evidence --- CHANGELOG.md | 10 ++ docs/STATUS.md | 9 + docs/engineering/esp32-boot-verification.md | 157 ++++++++++++++++++ docs/engineering/esp32-dual-core.md | 10 ++ docs/engineering/esp32-tecnoiot-b07n97dd1b.md | 11 ++ tests/vendor/README.md | 8 + .../boot-verification-2026-11-05.json | 84 ++++++++++ .../evidence/esp-idf-v4.4.7-qemu-hello.log | 139 ++++++++++++++++ tests/vendor/xtensa_integer/probe_rom_boot.py | 89 ++++++++++ 9 files changed, 517 insertions(+) create mode 100644 docs/engineering/esp32-boot-verification.md create mode 100644 tests/vendor/xtensa_integer/evidence/boot-verification-2026-11-05.json create mode 100644 tests/vendor/xtensa_integer/evidence/esp-idf-v4.4.7-qemu-hello.log create mode 100644 tests/vendor/xtensa_integer/probe_rom_boot.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ff5c3624..f3328c85f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Original ESP32 boot verification evidence + +- Built unmodified ESP-IDF v4.4.7 hello_world and observed its bootloader, + both CPU schedulers and application in Espressif QEMU. +- Added an external-ROM native diagnostic: the same image fails after seven + retired instructions at the unimplemented eFuse read, PC 0x4000fdd8/address + 0x3ff5a000. This is a confirmed native boot blocker, not SDK/board support. +- Rechecked the advertised module's nominal capacity against Espressif's + datasheet; the physical flash manufacturer/part remains unidentified. + ### Experimental explicit W25Q32JV flash programming - Added an explicitly selected, unprotected W25Q32JV-IQ 4 MiB profile with diff --git a/docs/STATUS.md b/docs/STATUS.md index e8d26ae71..4032c6f5e 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -7,6 +7,15 @@ does not mean the described behaviour exists. ## Original ESP32 dual-core target investigation (2026-11-04) +**Boot verification, 2026-11-05:** a pinned unmodified ESP-IDF v4.4.7 example +boots both schedulers and prints Hello world in Espressif QEMU. Native EmbSIM +using the same image and QEMU-distributed PRO ROM fails after seven retired +instructions: PC 0x4000fdd8, eFuse read 0x3ff5a000. This is a confirmed boot +blocker, not successful SDK/board boot. The physical flash ID remains pending; +4 MB is only the genuine ESP-WROOM-32 family's nominal datasheet capacity. +[Evidence and reproduction](engineering/esp32-boot-verification.md) separate +reference success, native failure and unresolved hardware identity. + The owner requested an original dual-core ESP32 development board. The listing is now tied to TECNOIOT ASIN B07N97DD1B: the advertised photo shows a 30-pin ESP-WROOM-32 board. Exact PCB revision and BOM remain unverified. diff --git a/docs/engineering/esp32-boot-verification.md b/docs/engineering/esp32-boot-verification.md new file mode 100644 index 000000000..1826a4746 --- /dev/null +++ b/docs/engineering/esp32-boot-verification.md @@ -0,0 +1,157 @@ +# ESP32 physical flash identity and ROM/SDK boot verification + +**Result on 2026-11-05: the actual board flash part is not identified. The pinned +ESP-IDF example boots in Espressif QEMU; native EmbSIM ROM boot fails at the first +eFuse-block read after seven retired instructions.** Passing instruction/flash +fixtures and CI must not be reported as successful board or SDK boot. + +## Physical flash: what the evidence establishes + +The owner's [TECNOIOT ASIN B07N97DD1B](https://www.amazon.fr/dp/B07N97DD1B) was +revisited. Its product title, feature bullets and overview do not identify a +flash manufacturer, JEDEC ID or order code. Related-product recommendations +are separate products and provide no evidence for this board. The existing +[advertised-board inventory](esp32-tecnoiot-b07n97dd1b.md) remains provisional. + +The advertised photo shows an ESP-WROOM-32 shield. Espressif's +[ESP32-WROOM-32 datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-wroom-32_datasheet_en.pdf) +specifies 4 MB external SPI flash. This is a **conditional nominal capacity** for +the identified genuine module family, not proof of the shipped unit's identity +or capacity. PDF SHA-256 inspected here: +`fe7dced2f6077f983dcd7df0e72b6f6c8b7b77fd6de2ff1a0efceffe3fd1e1fa`. +It does not identify this unit's flash manufacturer or full ordering suffix. + +There is no physical board/USB serial device in this cloud environment. The +owner has macOS/Linux; a read-only hardware result has been requested: + +```sh +python3 -m esptool --chip esp32 --port PORT flash-id +``` + +Use the actual serial port, for example `/dev/cu.SLAB_USBtoUART` or a matching +`/dev/cu.usbserial-*` device on macOS, `/dev/ttyUSB0` on Linux. Esptool v4 uses the +older command spelling `flash_id`. This reads identification without programming +flash, but the normal connection resets the board. The manufacturer/device ID, +detected size and silicon revision lines can establish more of the hardware +profile. An ID shared by multiple part revisions does not establish the exact +ordering suffix; markings or a verified BOM may still be necessary. + +The implemented W25Q32JV-IQ fixture therefore stays explicitly selected and +unverified against the owner's physical unit. Neither the SDK's configured +flash size nor QEMU's simulated part is hardware identification evidence. + +## Positive reference: real SDK example in Espressif QEMU + +Built ESP-IDF **v4.4.7**, commit +`38eeba213aa695aabfd6d89aa9f5078dbe5a94c3`, with Espressif's actual ESP32 GCC +8.4.0 esp-2021r2-patch5. Source in `examples/get-started/hello_world` and its +bootloader was unchanged. Configuration used the normal ESP32 target and +`COMPONENTS=main`; CMake 4.4.4 needed `CMAKE_POLICY_VERSION_MINIMUM=3.5` for this +older SDK. Build timestamps mean rebuilt binary hashes can differ; the exact +inputs tested are recorded in the evidence artifact. + +Copied the bootloader at 0x1000, partition table at 0x8000 and application at +0x10000 according to generated `flasher_args.json`, into an explicitly supplied +4 MiB erased backing. No application or bootloader bytes were patched. + +Espressif QEMU release `esp-develop-9.2.2-20260417` observed: + +```text +ESP-IDF v4.4.7 2nd stage bootloader +Pro cpu up. +App cpu up. +Starting scheduler on PRO CPU. +Starting scheduler on APP CPU. +Hello world! +``` + +The [captured UART log](../../tests/vendor/xtensa_integer/evidence/esp-idf-v4.4.7-qemu-hello.log) +contains the bootloader/app loading and application countdown. The host stopped +QEMU after 15 seconds; this was an intentional capture limit. It establishes +this example's ROM-to-bootloader-to-two-core-scheduler/application path in QEMU, +not every SDK function, physical timing or any native EmbSIM boot behavior. + +QEMU selects `gd25q32` for a 4 MiB backing; this is its configured model, not a +measurement of the owner's board. The SDK's default image header specifies +2 MiB, and its log explicitly warns that the simulated chip reports 4 MiB. +The application's printed 2 MiB follows the header limit. This illustrates why +configuration or printed SDK capacity must not substitute for hardware identity. + +## Native EmbSIM result: blocked + +The same merged image and Espressif QEMU-distributed PRO ROM were supplied to +the native machine. No firmware PC was set past reset and no missing MMIO +values were supplied to force boot onward. Result: + +```text +reset PC 0x40000400 +retired instructions 7 +failure PC 0x4000fdd8 +failure opcode 0x00a8 +access address 0x3ff5a000 +fault load failure (kind 5), status -1 +``` + +Pinned SDK `soc/esp32/include/soc/efuse_reg.h` identifies that address as +`EFUSE_BLK0_RDATA0_REG`. The current bus has no eFuse controller/profile. This +is the **first observed blocker**, not a claim that adding one register would +finish boot. RTC/strapping/reset state, further eFuse behavior, dedicated flash +commands/cache modes, other MMIO and complete CPU/SDK execution remain separate +requirements. Dummy zero reads would hide this gap and cannot verify boot. + +The probe uses the supported CPU reset profile and an explicit PRID=0 fixture, +not a silicon-qualified full PRID. It installs only the PRO ROM; APP-specific +ROM behavior is not tested. Missing DROM tail bytes are zero-filled to match the +QEMU region setup, not asserted as physical ROM contents. The blob is an +Espressif QEMU distribution input, not an authenticated dump of the owner's chip. +No vendor ROM/QEMU implementation is embedded in the application or repository. + +## Reproduction and artifacts + +[Machine-readable evidence](../../tests/vendor/xtensa_integer/evidence/boot-verification-2026-11-05.json) +records source revisions, tested binary hashes, reference milestones and native +failure details. QEMU archive SHA-256: +`0eecb2a34a5586c0e59110f77b9343b7b336e82fdb0e1a30e1dc1bab8a547e35`. +The archive is available from [Espressif's release](https://github.com/espressif/qemu/releases/tag/esp-develop-9.2.2-20260417). +PRO ROM SHA-256: +`9115bbcd03da9f54d3baca5ad94d588a43da54ed332347137ba3dc9010df258b`. + +With the SDK/toolchain/Python environment installed externally: + +```sh +idf.py -C /path/to/esp-idf/examples/get-started/hello_world -B /tmp/idf-hello \ + -D COMPONENTS=main -D CMAKE_POLICY_VERSION_MINIMUM=3.5 set-target esp32 build +``` + +Construct a 4 MiB backing from the build's actual files, without modifying them: + +```python +import json +from pathlib import Path +build = Path('/tmp/idf-hello') +image = bytearray(b'\xff' * 0x400000) +for offset, name in json.loads((build / 'flasher_args.json').read_text(encoding='utf-8'))['flash_files'].items(): + data = (build / name).read_bytes() + start = int(offset, 0) + image[start:start + len(data)] = data +Path('/tmp/idf-hello-flash.bin').write_bytes(image) +``` + +Run the reference with the release's ROM files in its default share directory: + +```sh +qemu-system-xtensa -M esp32 -nographic -nic none \ + -drive file=/tmp/idf-hello-flash.bin,if=mtd,format=raw +``` + +Run the native diagnostic from the repository root: + +```sh +python3 tests/vendor/xtensa_integer/probe_rom_boot.py \ + --rom /path/to/qemu/share/qemu/esp32-v3-rom.bin \ + --flash /tmp/idf-hello-flash.bin --report /tmp/native-boot.json +``` + +The diagnostic deliberately returns **1** for blocked/inconclusive boot; it is +not registered as a passing CTest/vendor qualification. A step budget without +an observed application-success validator cannot establish boot success. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 411625aab..be1984793 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -10,6 +10,16 @@ The listing brand is TECNOIOT. Its main image shows an ESP-WROOM-32 shield, advertised form factor, not a guaranteed PCB revision or bill of materials. See the [listing evidence and header transcription](esp32-tecnoiot-b07n97dd1b.md). +## ROM/SDK boot verification result + +A [direct boot investigation](esp32-boot-verification.md) on 2026-11-05 built +unmodified ESP-IDF v4.4.7 hello_world. It boots both CPU schedulers and prints +Hello world in Espressif QEMU. Native EmbSIM with the same image and the +QEMU-distributed PRO ROM fails after seven retired instructions at PC 0x4000fdd8, +reading unimplemented eFuse register 0x3ff5a000. No dummy MMIO values or host PC +shortcut were used. This confirms a native boot blocker; it does not qualify +physical ROM/board identity or imply that fixing one register finishes boot. + ## Reference material Primary references inspected for this investigation: diff --git a/docs/engineering/esp32-tecnoiot-b07n97dd1b.md b/docs/engineering/esp32-tecnoiot-b07n97dd1b.md index dc4f7a3f2..351dba399 100644 --- a/docs/engineering/esp32-tecnoiot-b07n97dd1b.md +++ b/docs/engineering/esp32-tecnoiot-b07n97dd1b.md @@ -23,6 +23,17 @@ Its 30-pin layout resembles boards commonly sold as ESP32 DevKit V1, but that name alone does not establish manufacturer, revision or electrical equivalence. Marketplace images and shipped units can differ. +## Flash verification update (2026-11-05) + +The listing was rechecked: its product-specific title/bullets/overview do not +identify the fitted flash manufacturer or ordering code. Related-product ads +are not evidence for this unit. Espressif's original ESP32-WROOM-32 datasheet +specifies nominal 4 MB external flash, conditional on genuine/correct module +identity; that does not identify this shipped unit's flash. A physical readout +has been requested from the owner's macOS/Linux machine. See the +[flash identity and real boot investigation](esp32-boot-verification.md). +The selected W25Q32JV-IQ test profile is not verified board hardware. + ## Header transcription Orientation: component side facing the viewer, antenna at top, USB connector diff --git a/tests/vendor/README.md b/tests/vendor/README.md index a5a7e03b1..d8c67b28a 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -182,3 +182,11 @@ replays, 125,856 retired instructions. Time is an explicit external fixture, not derived from instruction counts. This named unprotected chip profile is not a board BOM, full chip or ROM/SDK qualification. See [programming scope and reproduction](../../docs/engineering/esp32-flash-programming.md). + +`xtensa_integer/probe_rom_boot.py --rom --flash [--report]` is a diagnostic, +not a passing vendor qualification. With the pinned Espressif QEMU PRO ROM and +an actual unmodified ESP-IDF v4.4.7 hello_world image, native execution fails +after seven retired instructions at the missing eFuse read. The same image +boots both schedulers and the application in Espressif QEMU. The probe returns +1 for blocked/inconclusive boot, and does not invent MMIO or jump past reset. +See [boot verification and artifacts](../../docs/engineering/esp32-boot-verification.md). diff --git a/tests/vendor/xtensa_integer/evidence/boot-verification-2026-11-05.json b/tests/vendor/xtensa_integer/evidence/boot-verification-2026-11-05.json new file mode 100644 index 000000000..8e4685d5e --- /dev/null +++ b/tests/vendor/xtensa_integer/evidence/boot-verification-2026-11-05.json @@ -0,0 +1,84 @@ +{ + "date": "2026-11-05", + "implementation_under_test": "d4e34ae3dc84a2c23d72fa04f1f16be9369373de", + "hardware_flash_identity_verified": false, + "hardware_evidence": "Owner supplied ASIN; fresh product sections do not identify flash manufacturer/order code. Physical readout pending.", + "nominal_module": { + "photo_marking": "ESP-WROOM-32", + "datasheet_flash": "4 MB external SPI flash, conditional on correct/genuine module identity", + "datasheet_sha256": "fe7dced2f6077f983dcd7df0e72b6f6c8b7b77fd6de2ff1a0efceffe3fd1e1fa" + }, + "sdk": { + "tag": "v4.4.7", + "commit": "38eeba213aa695aabfd6d89aa9f5078dbe5a94c3", + "example": "examples/get-started/hello_world", + "source_modified": false, + "compiler": "xtensa-esp32-elf GCC 8.4.0 esp-2021r2-patch5", + "cmake": "4.4.4", + "sdkconfig_header_sha256": "54f4f0fc74d8a11d1b633cf8100b8be98ea1a5a2b3881bc70351a0e601b37599", + "example_source_sha256": "b924d02fa10270296068c5257f936fce1060c1aed1c7450e1ace57cddfa54835" + }, + "images": { + "bootloader/bootloader.bin": { + "bytes": 25632, + "sha256": "05b787f1f073586067a74d6009e1236215045f2543df1d9dd8df9f1b39ced4e2" + }, + "partition_table/partition-table.bin": { + "bytes": 3072, + "sha256": "7f00b6c042a89b15b0cac534f82ed988caf29278ff5700b0c511eb1b5bb7c820" + }, + "hello_world.bin": { + "bytes": 173088, + "sha256": "f2745148b536759215badf6d3927ecd4561c4fb443d5f5fc51baf84ac74e3acc" + }, + "merged_flash": { + "bytes": 4194304, + "sha256": "52fcca3c31ed835d45e22826d42dd7a7db0c867171fa330f9c56b0a81df7bee7" + } + }, + "reference": { + "tool": "Espressif QEMU 9.2.2 esp_develop_9.2.2_20260417", + "release": "esp-develop-9.2.2-20260417", + "archive_sha256": "0eecb2a34a5586c0e59110f77b9343b7b336e82fdb0e1a30e1dc1bab8a547e35", + "example_boot_observed": true, + "markers": [ + "ESP-IDF v4.4.7 2nd stage bootloader", + "Pro cpu up.", + "App cpu up.", + "Starting scheduler on PRO CPU.", + "Starting scheduler on APP CPU.", + "Hello world!" + ], + "flash_model": "gd25q32 selected by QEMU from 4 MiB backing, not a hardware identification", + "stop": "intentional 15-second host timeout while example countdown was running, not firmware failure" + }, + "native": { + "boot_verified": false, + "outcome": "blocked", + "core": "PRO only", + "rom_sha256": "9115bbcd03da9f54d3baca5ad94d588a43da54ed332347137ba3dc9010df258b", + "flash_sha256": "52fcca3c31ed835d45e22826d42dd7a7db0c867171fa330f9c56b0a81df7bee7", + "reset_vector": "0x40000400", + "prid_fixture": "0x00000000 (not silicon-qualified)", + "rom_tail_policy": "zero-fill absent DROM tail to match QEMU region setup, not silicon contents", + "flash_profile": "immutable SPI1 fixture, not a physical flash identity", + "status": -1, + "event": 0, + "pc": "0x4000fdd8", + "opcode": "0xa8", + "address": "0x3ff5a000", + "fault_kind": 5, + "attempts": 8, + "retired_before_stop": 7, + "trace_tail": [ + "0x40000400", + "0x4000fda0", + "0x4000fda3", + "0x4000fda6", + "0x4000fda9", + "0x4000fdac", + "0x4000fdd5", + "0x4000fdd8" + ] + } +} diff --git a/tests/vendor/xtensa_integer/evidence/esp-idf-v4.4.7-qemu-hello.log b/tests/vendor/xtensa_integer/evidence/esp-idf-v4.4.7-qemu-hello.log new file mode 100644 index 000000000..4eef5d993 --- /dev/null +++ b/tests/vendor/xtensa_integer/evidence/esp-idf-v4.4.7-qemu-hello.log @@ -0,0 +1,139 @@ +Adding SPI flash device +ets Jul 29 2019 12:21:46 + +rst:0x1 (POWERON_RESET),boot:0x12 (SPI_FAST_FLASH_BOOT) +configsip: 0, SPIWP:0xee +clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 +mode:DIO, clock div:2 +load:0x3fff0030,len:6652 +ho 0 tail 12 room 4 +load:0x40078000,len:15052 +load:0x40080400,len:3836 +entry 0x4008069c +I (742) boot: ESP-IDF v4.4.7 2nd stage bootloader +I (743) boot: compile time 14:52:36 +I (743) boot: Multicore bootloader +I (751) boot: chip revision: v0.0 +I (753) boot.esp32: SPI Speed : 40MHz +I (754) boot.esp32: SPI Mode : DIO +I (754) boot.esp32: SPI Flash Size : 2MB +I (761) boot: Enabling RNG early entropy source... +I (776) boot: Partition Table: +I (776) boot: ## Label Usage Type ST Offset Length +I (777) boot: 0 nvs WiFi data 01 02 00009000 00006000 +I (777) boot: 1 phy_init RF data 01 01 0000f000 00001000 +I (777) boot: 2 factory factory app 00 00 00010000 00100000 +I (782) boot: End of partition table +I (792) esp_image: segment 0: paddr=00010020 vaddr=3f400020 size=08094h ( 32916) map +I (818) esp_image: segment 1: paddr=000180bc vaddr=3ffb0000 size=01b7ch ( 7036) load +I (837) esp_image: segment 2: paddr=00019c40 vaddr=40080000 size=063d8h ( 25560) load +I (864) esp_image: segment 3: paddr=00020020 vaddr=400d0020 size=15108h ( 86280) map +I (892) esp_image: segment 4: paddr=00035130 vaddr=400863d8 size=052c0h ( 21184) load +I (913) boot: Loaded app from partition at offset 0x10000 +I (913) boot: Disabling RNG early entropy source... +I (925) cpu_start: Multicore app +I (931) cpu_start: Pro cpu up. +I (932) cpu_start: Starting app cpu, entry point is 0x40080ff0 +I (569) cpu_start: App cpu up. +I (1549) cpu_start: Pro cpu start user code +I (1550) cpu_start: cpu freq: 160000000 +I (1551) cpu_start: Application information: +I (1551) cpu_start: Project name: hello_world +I (1551) cpu_start: App version: v4.4.7 +I (1551) cpu_start: Compile time: Nov 5 2026 14:52:30 +I (1551) cpu_start: ELF file SHA256: fe26855a26fcde4c... +I (1552) cpu_start: ESP-IDF: v4.4.7 +I (1552) cpu_start: Min chip rev: v0.0 +I (1552) cpu_start: Max chip rev: v3.99 +I (1552) cpu_start: Chip rev: v0.0 +I (1554) heap_init: Initializing. RAM available for dynamic allocation: +I (1555) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM +I (1555) heap_init: At 3FFB2470 len 0002DB90 (182 KiB): DRAM +I (1555) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM +I (1555) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM +I (1556) heap_init: At 4008B698 len 00014968 (82 KiB): IRAM +I (1593) spi_flash: detected chip: gd +I (1606) spi_flash: flash io: dio +W (1613) spi_flash: Detected size(4096k) larger than the size in the binary image header(2048k). Using the size in the binary image header. +I (1621) cpu_start: Starting scheduler on PRO CPU. +I (0) cpu_start: Starting scheduler on APP CPU. +Hello world! +This is esp32 chip with 2 CPU core(s), WiFi/BT/BLE, silicon revision v0.0, 2MB external flash +Minimum free heap size: 300920 bytes +Restarting in 10 seconds... +Restarting in 9 seconds... +Restarting in 8 seconds... +Restarting in 7 seconds... +Restarting in 6 seconds... +Restarting in 5 seconds... +Restarting in 4 seconds... +Restarting in 3 seconds... +Restarting in 2 seconds... +Restarting in 1 seconds... +Restarting in 0 seconds... +Restarting now. +ets Jul 29 2019 12:21:46 + +rst:0xc (SW_CPU_RESET),boot:0x12 (SPI_FAST_FLASH_BOOT) +configsip: 0, SPIWP:0xee +clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 +mode:DIO, clock div:2 +load:0x3fff0030,len:6652 +ho 0 tail 12 room 4 +load:0x40078000,len:15052 +load:0x40080400,len:3836 +entry 0x4008069c +I (27883) boot: ESP-IDF v4.4.7 2nd stage bootloader +I (27883) boot: compile time 14:52:36 +I (27883) boot: Multicore bootloader +I (27891) boot: chip revision: v0.0 +I (27894) boot.esp32: SPI Speed : 40MHz +I (27894) boot.esp32: SPI Mode : DIO +I (27894) boot.esp32: SPI Flash Size : 2MB +I (27902) boot: Enabling RNG early entropy source... +I (27916) boot: Partition Table: +I (27916) boot: ## Label Usage Type ST Offset Length +I (27916) boot: 0 nvs WiFi data 01 02 00009000 00006000 +I (27916) boot: 1 phy_init RF data 01 01 0000f000 00001000 +I (27916) boot: 2 factory factory app 00 00 00010000 00100000 +I (27921) boot: End of partition table +I (27933) esp_image: segment 0: paddr=00010020 vaddr=3f400020 size=08094h ( 32916) map +I (27953) esp_image: segment 1: paddr=000180bc vaddr=3ffb0000 size=01b7ch ( 7036) load +I (27972) esp_image: segment 2: paddr=00019c40 vaddr=40080000 size=063d8h ( 25560) load +I (28014) esp_image: segment 3: paddr=00020020 vaddr=400d0020 size=15108h ( 86280) map +I (28040) esp_image: segment 4: paddr=00035130 vaddr=400863d8 size=052c0h ( 21184) load +I (28066) boot: Loaded app from partition at offset 0x10000 +I (28066) boot: Disabling RNG early entropy source... +I (28076) cpu_start: Multicore app +I (28083) cpu_start: Pro cpu up. +I (28084) cpu_start: Starting app cpu, entry point is 0x40080ff0 +I (12234) cpu_start: App cpu up. +I (13513) cpu_start: Pro cpu start user code +I (13513) cpu_start: cpu freq: 160000000 +I (13513) cpu_start: Application information: +I (13513) cpu_start: Project name: hello_world +I (13513) cpu_start: App version: v4.4.7 +I (13513) cpu_start: Compile time: Nov 5 2026 14:52:30 +I (13514) cpu_start: ELF file SHA256: fe26855a26fcde4c... +I (13514) cpu_start: ESP-IDF: v4.4.7 +I (13514) cpu_start: Min chip rev: v0.0 +I (13514) cpu_start: Max chip rev: v3.99 +I (13514) cpu_start: Chip rev: v0.0 +I (13515) heap_init: Initializing. RAM available for dynamic allocation: +I (13516) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM +I (13516) heap_init: At 3FFB2470 len 0002DB90 (182 KiB): DRAM +I (13516) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM +I (13517) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM +I (13517) heap_init: At 4008B698 len 00014968 (82 KiB): IRAM +I (13540) spi_flash: detected chip: gd +I (13552) spi_flash: flash io: dio +W (13557) spi_flash: Detected size(4096k) larger than the size in the binary image header(2048k). Using the size in the binary image header. +I (13563) cpu_start: Starting scheduler on PRO CPU. +I (0) cpu_start: Starting scheduler on APP CPU. +Hello world! +This is esp32 chip with 2 CPU core(s), WiFi/BT/BLE, silicon revision v0.0, 2MB external flash +Minimum free heap size: 300920 bytes +Restarting in 10 seconds... +Restarting in 9 seconds... +Restarting in 8 seconds... +qemu-system-xtensa: terminating on signal 15 from pid 306234 (timeout) diff --git a/tests/vendor/xtensa_integer/probe_rom_boot.py b/tests/vendor/xtensa_integer/probe_rom_boot.py new file mode 100644 index 000000000..4206e8120 --- /dev/null +++ b/tests/vendor/xtensa_integer/probe_rom_boot.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""Diagnose the first native blocker using external Espressif QEMU PRO ROM. + +This is a boot investigation, not a passing conformance suite. Returns 1 when +blocked or inconclusive. No vendor bytes embedded, actual board identity claimed, +unknown MMIO supplied, or host PC shortcut past reset. PRO only; no UART success +validator or APP-specific ROM provider, so a step budget cannot establish boot. +""" +from collections import deque +import argparse +import ctypes as C +import hashlib +import json +from pathlib import Path +import subprocess +import tempfile +from run_execute import ROOT, Fault +from run_windows_c import Windows +from run_machine_c import Allocator + +ROM_SHA256='9115bbcd03da9f54d3baca5ad94d588a43da54ed332347137ba3dc9010df258b' + + +def main(): + ap=argparse.ArgumentParser(description=__doc__) + ap.add_argument('--rom',type=Path,required=True);ap.add_argument('--flash',type=Path,required=True) + ap.add_argument('--report',type=Path);ap.add_argument('--steps',type=int,default=100000) + ap.add_argument('--cc',default='cc');args=ap.parse_args() + if not 1<=args.steps<=1000000:ap.error('--steps must be 1..1000000') + rom=args.rom.read_bytes();image=args.flash.read_bytes() + if hashlib.sha256(rom).hexdigest()!=ROM_SHA256:ap.error('expected pinned Espressif QEMU 9.2.2 PRO ROM') + if not image or len(image)%65536 or len(image)>0x1000000:ap.error('flash must be 1..256 complete 64KiB pages') + with tempfile.TemporaryDirectory(prefix='embsim-native-rom-probe-') as tmp: + lib=Path(tmp)/'probe.so' + sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','machine.c','matrix.c','flashmap.c','flashspi.c','w25q32.c']] + sources += [ROOT/'core/util/alloc.c'] + subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC', + '-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) + api=C.CDLL(str(lib));pointer=C.c_void_p + def bind(name,result,*params): + f=getattr(api,name);f.restype=result;f.argtypes=list(params);return f + init=bind('esk_alloc_init',None,C.POINTER(Allocator),pointer) + create=bind('esk_esp32_machine_create',C.c_int,C.POINTER(Allocator),C.POINTER(pointer)) + destroy=bind('esk_esp32_machine_destroy',None,pointer) + fabric=bind('esk_esp32_machine_fabric',pointer,pointer) + install=bind('esk_esp32_fabric_rom',C.c_int,pointer,C.c_uint,pointer,C.c_size_t) + flash=bind('esk_esp32_fabric_flash',C.c_int,pointer,pointer,C.c_size_t) + spi=bind('esk_esp32_fabric_spi1_configure',C.c_int,pointer,C.c_int) + core=bind('esk_esp32_machine_core',C.POINTER(Windows),pointer,C.c_uint) + reset=bind('esk_xtensa_lx6_reset',C.c_int,C.POINTER(Windows),C.c_uint) + prid=bind('esk_xtensa_prid_configure',C.c_int,C.POINTER(Windows),C.c_uint32) + run=bind('esk_esp32_machine_run_core',C.c_int,pointer,C.c_uint,C.c_int) + step=bind('esk_esp32_machine_step',C.c_int,pointer,C.c_uint,C.POINTER(C.c_uint),C.POINTER(Fault)) + retired=bind('esk_esp32_machine_retired',C.c_uint64,pointer,C.c_uint) + allocator=Allocator();init(C.byref(allocator),None);machine=pointer() + assert create(C.byref(allocator),C.byref(machine))==0 + report={'boot_verified':False,'outcome':'step_budget_inconclusive','core':'PRO only', + 'rom_sha256':ROM_SHA256,'flash_sha256':hashlib.sha256(image).hexdigest(), + 'reset_vector':'0x40000400','prid_fixture':'0x00000000 (not silicon-qualified)', + 'rom_tail_policy':'zero-fill absent DROM tail to match QEMU region setup, not silicon contents', + 'flash_profile':'immutable SPI1 fixture, not a physical flash identity'} + try: + ram=fabric(machine);data=rom[:0x60000] + assert install(ram,0,C.c_char_p(data),len(data))==0 + data=rom[0x60000:]+bytes(0x70000-len(rom));assert len(data)==65536 + assert install(ram,1,C.c_char_p(data),len(data))==0 + assert flash(ram,C.c_char_p(image),len(image))==0 and spi(ram,1)==0 + w=core(machine,0);assert reset(w,1)==0 and prid(w,0)==0 and run(machine,0,1)==0 + event=C.c_uint();fault=Fault();trace=deque(maxlen=12) + for attempt in range(args.steps): + pc=w.contents.cpu.pc;status=step(machine,0,C.byref(event),C.byref(fault));trace.append(hex(pc)) + if status or event.value>=18: + report.update(outcome='blocked' if status else 'suspended',status=status,event=event.value, + pc=hex(fault.pc if status else pc),opcode=hex(fault.opcode),address=hex(fault.address), + fault_kind=fault.kind,attempts=attempt+1) + break + report.update(retired_before_stop=retired(machine,0),trace_tail=list(trace)) + finally:destroy(machine) + assert allocator.live_bytes==0 + output=json.dumps(report,indent=2)+'\n' + if args.report:args.report.write_text(output,encoding='utf-8') + print(output,end='') + return 1 + + +if __name__=='__main__':raise SystemExit(main()) From b4c708080efddc71e041870cffc4f807751c987c Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 15:00:42 +0000 Subject: [PATCH 25/57] Normalize captured UART evidence formatting --- .../xtensa_integer/evidence/boot-verification-2026-11-05.json | 3 ++- .../xtensa_integer/evidence/esp-idf-v4.4.7-qemu-hello.log | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/vendor/xtensa_integer/evidence/boot-verification-2026-11-05.json b/tests/vendor/xtensa_integer/evidence/boot-verification-2026-11-05.json index 8e4685d5e..4d5fe0fc6 100644 --- a/tests/vendor/xtensa_integer/evidence/boot-verification-2026-11-05.json +++ b/tests/vendor/xtensa_integer/evidence/boot-verification-2026-11-05.json @@ -50,7 +50,8 @@ "Hello world!" ], "flash_model": "gd25q32 selected by QEMU from 4 MiB backing, not a hardware identification", - "stop": "intentional 15-second host timeout while example countdown was running, not firmware failure" + "stop": "intentional 15-second host timeout while example countdown was running, not firmware failure", + "uart_log_normalization": "ANSI colors removed; LF line endings; trailing whitespace removed" }, "native": { "boot_verified": false, diff --git a/tests/vendor/xtensa_integer/evidence/esp-idf-v4.4.7-qemu-hello.log b/tests/vendor/xtensa_integer/evidence/esp-idf-v4.4.7-qemu-hello.log index 4eef5d993..185ea7d1c 100644 --- a/tests/vendor/xtensa_integer/evidence/esp-idf-v4.4.7-qemu-hello.log +++ b/tests/vendor/xtensa_integer/evidence/esp-idf-v4.4.7-qemu-hello.log @@ -44,7 +44,7 @@ I (1551) cpu_start: Compile time: Nov 5 2026 14:52:30 I (1551) cpu_start: ELF file SHA256: fe26855a26fcde4c... I (1552) cpu_start: ESP-IDF: v4.4.7 I (1552) cpu_start: Min chip rev: v0.0 -I (1552) cpu_start: Max chip rev: v3.99 +I (1552) cpu_start: Max chip rev: v3.99 I (1552) cpu_start: Chip rev: v0.0 I (1554) heap_init: Initializing. RAM available for dynamic allocation: I (1555) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM @@ -117,7 +117,7 @@ I (13513) cpu_start: Compile time: Nov 5 2026 14:52:30 I (13514) cpu_start: ELF file SHA256: fe26855a26fcde4c... I (13514) cpu_start: ESP-IDF: v4.4.7 I (13514) cpu_start: Min chip rev: v0.0 -I (13514) cpu_start: Max chip rev: v3.99 +I (13514) cpu_start: Max chip rev: v3.99 I (13514) cpu_start: Chip rev: v0.0 I (13515) heap_init: Initializing. RAM available for dynamic allocation: I (13516) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM From edb078ca123ed8c0ab314984a726fc976ca9ad01 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 15:09:25 +0000 Subject: [PATCH 26/57] Record physical ESP32 revision and Giantec flash identification --- CHANGELOG.md | 12 ++- docs/STATUS.md | 11 ++- docs/engineering/esp32-boot-verification.md | 91 +++++++++++++------ docs/engineering/esp32-dual-core.md | 9 +- docs/engineering/esp32-flash-programming.md | 9 +- docs/engineering/esp32-tecnoiot-b07n97dd1b.md | 34 ++++--- .../boot-verification-2026-11-05.json | 7 +- .../hardware-identification-2026-11-05.json | 57 ++++++++++++ .../hardware-identification-2026-11-05.txt | 17 ++++ 9 files changed, 193 insertions(+), 54 deletions(-) create mode 100644 tests/vendor/xtensa_integer/evidence/hardware-identification-2026-11-05.json create mode 100644 tests/vendor/xtensa_integer/evidence/hardware-identification-2026-11-05.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index f3328c85f..6b37989f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Owner ESP32 hardware identification + +- Recorded the owner's physical esptool readout: ESP32-D0WD-V3 v3.1, 40 MHz + crystal, JEDEC C4 60 16 and 4 MB detected flash; 3.3 V is a reported strapping + setting. Unique MAC is omitted from repository artifacts. +- Matched the flash ID to Giantec's GT25Q32A-U manufacturer datasheet. Exact + ordering/package/revision remains unresolved; the separate Winbond test + fixture does not match this board. Runtime models are unchanged. + ### Original ESP32 boot verification evidence - Built unmodified ESP-IDF v4.4.7 hello_world and observed its bootloader, @@ -13,7 +22,8 @@ versions follow semantic versioning. There is no release yet. retired instructions at the unimplemented eFuse read, PC 0x4000fdd8/address 0x3ff5a000. This is a confirmed native boot blocker, not SDK/board support. - Rechecked the advertised module's nominal capacity against Espressif's - datasheet; the physical flash manufacturer/part remains unidentified. + datasheet; the subsequent owner readout above establishes the reported + JEDEC/vendor class, with exact ordering details still unresolved. ### Experimental explicit W25Q32JV flash programming diff --git a/docs/STATUS.md b/docs/STATUS.md index 4032c6f5e..05366dcfe 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -11,8 +11,11 @@ does not mean the described behaviour exists. boots both schedulers and prints Hello world in Espressif QEMU. Native EmbSIM using the same image and QEMU-distributed PRO ROM fails after seven retired instructions: PC 0x4000fdd8, eFuse read 0x3ff5a000. This is a confirmed boot -blocker, not successful SDK/board boot. The physical flash ID remains pending; -4 MB is only the genuine ESP-WROOM-32 family's nominal datasheet capacity. +blocker, not successful SDK/board boot. The owner subsequently supplied a +physical esptool readout: ESP32-D0WD-V3 v3.1, 40 MHz crystal, JEDEC C4 60 16, +4 MB detected flash and a 3.3 V strapping setting. Giantec's GT25Q32A-U datasheet +matches the ID; exact ordering/package remains unresolved. The Winbond fixture +is a different part and does not qualify this board. [Evidence and reproduction](engineering/esp32-boot-verification.md) separate reference success, native failure and unresolved hardware identity. @@ -136,8 +139,8 @@ extends the storage commands. An explicit [W25Q32JV-IQ programming profile](engineering/esp32-flash-programming.md) now adds write-enable/BUSY, page programming/wrap and aligned erase through SPI1. It requires an explicitly selected unprotected 4 MiB part fixture; the requested -board's actual flash chip remains unverified. External microseconds drive -conservative datasheet-maximum durations. Native tests and 2,631 independent +board reports Giantec-matching C4 60 16, different from this fixture's EF 40 16. +External microseconds drive conservative datasheet-maximum durations. Native tests and 2,631 independent command/time comparisons pass; compiled firmware programs and executes 32 RAM workers (32 exact replays, 125,856 retired instructions). Fresh post-completion flush is required before cache enable. Full chip/protection/electrical behavior, diff --git a/docs/engineering/esp32-boot-verification.md b/docs/engineering/esp32-boot-verification.md index 1826a4746..99a38278f 100644 --- a/docs/engineering/esp32-boot-verification.md +++ b/docs/engineering/esp32-boot-verification.md @@ -1,44 +1,75 @@ # ESP32 physical flash identity and ROM/SDK boot verification -**Result on 2026-11-05: the actual board flash part is not identified. The pinned -ESP-IDF example boots in Espressif QEMU; native EmbSIM ROM boot fails at the first -eFuse-block read after seven retired instructions.** Passing instruction/flash -fixtures and CI must not be reported as successful board or SDK boot. - -## Physical flash: what the evidence establishes - -The owner's [TECNOIOT ASIN B07N97DD1B](https://www.amazon.fr/dp/B07N97DD1B) was -revisited. Its product title, feature bullets and overview do not identify a -flash manufacturer, JEDEC ID or order code. Related-product recommendations -are separate products and provide no evidence for this board. The existing -[advertised-board inventory](esp32-tecnoiot-b07n97dd1b.md) remains provisional. +**Result on 2026-11-05: the owner's board reports ESP32-D0WD-V3 revision 3.1, +JEDEC C4 60 16 and 4 MB flash. Giantec's GT25Q32A-U datasheet matches that ID; +exact package/ordering suffix remains unconfirmed. The pinned SDK example boots +in Espressif QEMU; native EmbSIM fails at the eFuse read after seven retired +instructions.** Passing fixtures and CI do not establish successful native boot. + +## Physical readout and manufacturer match + +The owner ran esptool v5.6.0 on macOS against the physical board. The +[recorded output](../../tests/vendor/xtensa_integer/evidence/hardware-identification-2026-11-05.txt) +and [structured evidence](../../tests/vendor/xtensa_integer/evidence/hardware-identification-2026-11-05.json) +establish these reported values: + +| Item | Hardware/tool report | Scope | +|---|---|---| +| Silicon | ESP32-D0WD-V3, revision v3.1 | Identifies the responding chip/revision | +| Crystal | 40 MHz | Esptool detection, not an independent frequency measurement | +| Features | Wi-Fi, BT, dual core + LP core, 240 MHz, Vref calibration in eFuse, Coding Scheme None | Reported capabilities; 240 MHz is not a measured current CPU frequency | +| Flash manufacturer/device | C4 / 6016, JEDEC bytes C4 60 16 | Physical ID transaction through the running RAM stub | +| Flash capacity | 4 MB (4,194,304 bytes) | Detected capacity; not inferred from SDK configuration | +| Flash voltage | 3.3 V, set by strapping pin | Reported setting, not an electrical rail measurement | + +The MAC is omitted from repository artifacts because it is unnecessary for +part qualification. This readout verifies the responding serial-download/RAM-stub +path and flash-ID transaction. It does not prove a physical SDK application boot +or any native EmbSIM boot behavior. + +Giantec's [GT25Q32A-U datasheet V2.5, June 2026, section 9.2, page 36/88](https://en.giantec-semi.com/uploads/pdf/260605/GT25Q32A%20U%20DS_V2.5.pdf) +explicitly lists 9Fh response **C4h / 60 / 16**. PDF SHA-256: +`df021758e55bde131f39af59c42acac20f2e0b32e251f4ade673687d22b7cd55`. +This supports a Giantec GT25Q32-class identification and a match to the published +GT25Q32A-U ID. JEDEC alone does not prove an exact package, temperature/order +suffix, die revision or uniqueness across all related variants. Physical +markings or an authenticated BOM would resolve the remaining ordering details. + +The observed ID differs from the W25Q32JV-IQ fixture's EF 40 16. That Winbond +fixture must not be selected as verified hardware for this board or relabeled +with C4 without qualifying Giantec-specific behavior. No runtime chip profile +was changed by this evidence update; a Giantec model still needs qualification. + +## Listing and nominal module evidence + +The [TECNOIOT ASIN B07N97DD1B](https://www.amazon.fr/dp/B07N97DD1B) was also +revisited. Its product-specific fields do not identify the fitted flash part; +related-product recommendations are separate products. The +[advertised-board inventory](esp32-tecnoiot-b07n97dd1b.md) is still provisional +for PCB, module marking, power, bridge and header continuity. The advertised photo shows an ESP-WROOM-32 shield. Espressif's [ESP32-WROOM-32 datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-wroom-32_datasheet_en.pdf) -specifies 4 MB external SPI flash. This is a **conditional nominal capacity** for -the identified genuine module family, not proof of the shipped unit's identity -or capacity. PDF SHA-256 inspected here: +specifies nominal 4 MB external SPI flash, conditional on genuine/correct module +identity. PDF SHA-256: `fe7dced2f6077f983dcd7df0e72b6f6c8b7b77fd6de2ff1a0efceffe3fd1e1fa`. -It does not identify this unit's flash manufacturer or full ordering suffix. +The owner's actual 4 MB report is separate, stronger evidence for the responding +unit's detected capacity. Neither source authenticates a full PCB BOM. -There is no physical board/USB serial device in this cloud environment. The -owner has macOS/Linux; a read-only hardware result has been requested: +To repeat the read-only check on macOS/Linux, use the computer attached to the +board (no USB serial device is attached to this cloud environment): ```sh -python3 -m esptool --chip esp32 --port PORT flash-id +python3 -m venv ~/.venvs/embsim-board-check +~/.venvs/embsim-board-check/bin/python -m pip install esptool +~/.venvs/embsim-board-check/bin/python -m serial.tools.list_ports +~/.venvs/embsim-board-check/bin/python -m esptool --chip esp32 --port PORT flash-id ``` -Use the actual serial port, for example `/dev/cu.SLAB_USBtoUART` or a matching -`/dev/cu.usbserial-*` device on macOS, `/dev/ttyUSB0` on Linux. Esptool v4 uses the -older command spelling `flash_id`. This reads identification without programming -flash, but the normal connection resets the board. The manufacturer/device ID, -detected size and silicon revision lines can establish more of the hardware -profile. An ID shared by multiple part revisions does not establish the exact -ordering suffix; markings or a verified BOM may still be necessary. - -The implemented W25Q32JV-IQ fixture therefore stays explicitly selected and -unverified against the owner's physical unit. Neither the SDK's configured -flash size nor QEMU's simulated part is hardware identification evidence. +Replace PORT with the actual enumerated device. Esptool v4 uses `flash_id`. +The command does not program flash, but normally resets the board and executes +a RAM stub. The SDK's configured flash size and QEMU's simulated part remain +configuration choices, rather than hardware identification evidence. ## Positive reference: real SDK example in Espressif QEMU diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index be1984793..eed1d376f 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -20,6 +20,12 @@ reading unimplemented eFuse register 0x3ff5a000. No dummy MMIO values or host PC shortcut were used. This confirms a native boot blocker; it does not qualify physical ROM/board identity or imply that fixing one register finishes boot. +The owner subsequently supplied physical identification: ESP32-D0WD-V3 v3.1, +40 MHz crystal, JEDEC C4 60 16 and 4 MB detected flash. Giantec GT25Q32A-U +manufacturer documentation matches that ID; exact ordering/package remains +unresolved. This identifies the measured unit more precisely than the listing, +and differs from the separate Winbond test profile. + ## Reference material Primary references inspected for this investigation: @@ -833,7 +839,8 @@ named-part fixture below adds bounded program/erase behavior. The [named flash-part contract](esp32-flash-programming.md) adds an explicit unprotected W25Q32JV-IQ 4 MiB profile. It does not identify the requested board's -actual flash part. Standard write-enable/status, page program/wrap and aligned +actual flash part. The physical readout now matches Giantec C4 60 16, rather than +this fixture's EF 40 16. Standard write-enable/status, page program/wrap and aligned erase use supplied external microseconds with conservative datasheet-maximum durations. SPI1 USER transmit/receive and cache re-enable boundaries are covered. Native tests, 2,631 independent command/time comparisons and actual compiled diff --git a/docs/engineering/esp32-flash-programming.md b/docs/engineering/esp32-flash-programming.md index d0f2d83df..95f30b110 100644 --- a/docs/engineering/esp32-flash-programming.md +++ b/docs/engineering/esp32-flash-programming.md @@ -15,9 +15,12 @@ flash chip's programming completion are separate events. The model implements this behavior from Winbond W25Q32JV Revision G, March 27, 2018. IQ/JQ identifies as EF 40 16; IM/JM has a different type byte and is not -silently substituted. The requested TECNOIOT board's flash marking/BOM remain -unverified. Selecting this part is an explicit test configuration, not a board -identity conclusion. +silently substituted. The owner's physical board now reports JEDEC C4 60 16 / +4 MB, matching Giantec's GT25Q32A-U datasheet. Exact ordering/BOM details remain +unconfirmed. +That differs from this fixture's EF 40 16. See the +[physical readout evidence](esp32-boot-verification.md). Selecting this Winbond +part is a separate test configuration, not the owner's verified board profile. ## Qualified subset diff --git a/docs/engineering/esp32-tecnoiot-b07n97dd1b.md b/docs/engineering/esp32-tecnoiot-b07n97dd1b.md index 351dba399..00a9150bc 100644 --- a/docs/engineering/esp32-tecnoiot-b07n97dd1b.md +++ b/docs/engineering/esp32-tecnoiot-b07n97dd1b.md @@ -2,7 +2,8 @@ **Status: identified listing, provisional hardware inventory, not executable support.** Inspected 2026-11-04 after the owner provided the product link. -No physical unit, PCB schematic or hardware capture has been examined. +No direct physical inspection, PCB schematic or electrical capture has been +examined. An owner-supplied serial hardware identification is recorded below. ## Sources and confidence @@ -23,16 +24,21 @@ Its 30-pin layout resembles boards commonly sold as ESP32 DevKit V1, but that name alone does not establish manufacturer, revision or electrical equivalence. Marketplace images and shipped units can differ. -## Flash verification update (2026-11-05) +## Hardware verification update (2026-11-05) -The listing was rechecked: its product-specific title/bullets/overview do not -identify the fitted flash manufacturer or ordering code. Related-product ads -are not evidence for this unit. Espressif's original ESP32-WROOM-32 datasheet -specifies nominal 4 MB external flash, conditional on genuine/correct module -identity; that does not identify this shipped unit's flash. A physical readout -has been requested from the owner's macOS/Linux machine. See the -[flash identity and real boot investigation](esp32-boot-verification.md). -The selected W25Q32JV-IQ test profile is not verified board hardware. +Owner-supplied esptool v5.6.0 output reports **ESP32-D0WD-V3 revision v3.1**, +a **40 MHz crystal**, **JEDEC C4 60 16** and **4 MB flash**. Esptool reports a +3.3 V flash setting from a strapping pin; this is not a measured rail voltage. +Giantec GT25Q32A-U V2.5 section 9.2 lists that exact JEDEC response, supporting +a Giantec GT25Q32-class match. Exact package/ordering suffix and die revision +remain unconfirmed. The output and primary manufacturer reference are in the +[hardware and boot investigation](esp32-boot-verification.md). + +The listing itself still does not identify a complete flash part or authenticate +the photo/PCB revision. The Winbond W25Q32JV-IQ EF 40 16 fixture does not match +the physical readout and remains a separate test profile. Serial download/stub +success is not full ROM/SDK application boot evidence. No runtime part was +switched or relabeled from this report alone. ## Header transcription @@ -76,9 +82,11 @@ range. GPIOs are 3.3 V signals, not assumed 5 V tolerant. Before publishing an electrically qualified board model, establish: -- Actual PCB/module revision and ESP32 silicon/eFuse identity. -- Flash manufacturer/capacity and whether any PSRAM is fitted; neither is proven - by this photograph. Do not silently select a WROVER configuration. +- Actual PCB/module revision and full eFuse configuration. Silicon/revision is + now hardware-reported as ESP32-D0WD-V3 v3.1. +- Exact flash ordering/package/revision and whether any PSRAM is fitted. JEDEC + C4 60 16 / 4 MB and the Giantec datasheet match are now recorded; the photo + does not establish PSRAM. Do not silently select a WROVER configuration. - USB bridge exact device and wiring; CP210X in the product text proves neither a specific CP2102 variant nor its oscillator, power or flow-control connections. - Regulator suffix, power routing, protection, capacitor values and tolerances. diff --git a/tests/vendor/xtensa_integer/evidence/boot-verification-2026-11-05.json b/tests/vendor/xtensa_integer/evidence/boot-verification-2026-11-05.json index 4d5fe0fc6..e4f4247af 100644 --- a/tests/vendor/xtensa_integer/evidence/boot-verification-2026-11-05.json +++ b/tests/vendor/xtensa_integer/evidence/boot-verification-2026-11-05.json @@ -2,7 +2,7 @@ "date": "2026-11-05", "implementation_under_test": "d4e34ae3dc84a2c23d72fa04f1f16be9369373de", "hardware_flash_identity_verified": false, - "hardware_evidence": "Owner supplied ASIN; fresh product sections do not identify flash manufacturer/order code. Physical readout pending.", + "hardware_evidence": "Updated by owner esptool output: ESP32-D0WD-V3 v3.1; JEDEC C4 60 16 and 4 MiB; Giantec GT25Q32A-U datasheet match. Exact package/order suffix remains unresolved.", "nominal_module": { "photo_marking": "ESP-WROOM-32", "datasheet_flash": "4 MB external SPI flash, conditional on correct/genuine module identity", @@ -81,5 +81,8 @@ "0x4000fdd5", "0x4000fdd8" ] - } + }, + "hardware_flash_jedec_and_capacity_reported": true, + "hardware_evidence_artifact": "hardware-identification-2026-11-05.json", + "hardware_flash_identity_verification_scope": "Exact ordering/package/revision remains unresolved; reported JEDEC/capacity and manufacturer table match are separately recorded" } diff --git a/tests/vendor/xtensa_integer/evidence/hardware-identification-2026-11-05.json b/tests/vendor/xtensa_integer/evidence/hardware-identification-2026-11-05.json new file mode 100644 index 000000000..651659a3e --- /dev/null +++ b/tests/vendor/xtensa_integer/evidence/hardware-identification-2026-11-05.json @@ -0,0 +1,57 @@ +{ + "date": "2026-11-05", + "source": "Owner-provided read-only esptool flash-id output from the physical board, not cloud hardware access", + "tool": "esptool v5.6.0", + "host_platform": "macOS", + "chip": { + "reported_type": "ESP32-D0WD-V3", + "reported_revision": "3.1", + "reported_crystal_mhz": 40, + "reported_features": [ + "Wi-Fi", + "BT", + "Dual Core + LP Core", + "240MHz", + "Vref calibration in eFuse", + "Coding Scheme None" + ], + "clock_note": "240MHz is a reported capability, not a measured current execution frequency" + }, + "flash": { + "jedec_manufacturer": "0xc4", + "jedec_device": "0x6016", + "jedec_bytes": [ + "0xc4", + "0x60", + "0x16" + ], + "reported_size_bytes": 4194304, + "reported_voltage_setting_v": 3.3, + "voltage_source": "strapping pin setting reported by esptool; not a voltage measurement" + }, + "identity": { + "jedec_and_capacity_hardware_reported": true, + "manufacturer_match": "Giantec", + "matching_datasheet_family": "GT25Q32A-U", + "exact_ordering_suffix_verified": false, + "package_and_flash_die_revision_verified": false, + "confidence_note": "Published manufacturer ID table matches the readout; JEDEC alone does not prove package, suffix or all revision-specific behavior" + }, + "primary_reference": { + "title": "Giantec GT25Q32A-U datasheet V2.5, June 2026, section 9.2, page 36/88", + "url": "https://en.giantec-semi.com/uploads/pdf/260605/GT25Q32A%20U%20DS_V2.5.pdf", + "sha256": "df021758e55bde131f39af59c42acac20f2e0b32e251f4ade673687d22b7cd55" + }, + "observed_workflow": { + "serial_connection": true, + "ram_stub_running": true, + "flash_id_read": true, + "reset_via_rts_reported": true, + "physical_rom_spi_sdk_boot_proven": false, + "native_sdk_boot_proven": false + }, + "model_consequence": "W25Q32JV-IQ EF4016 is a separate test fixture and does not match this board readout; no runtime profile changed by this evidence update", + "redactions": [ + "MAC omitted because unique device identity is unnecessary for target qualification" + ] +} diff --git a/tests/vendor/xtensa_integer/evidence/hardware-identification-2026-11-05.txt b/tests/vendor/xtensa_integer/evidence/hardware-identification-2026-11-05.txt new file mode 100644 index 000000000..60a9ecf68 --- /dev/null +++ b/tests/vendor/xtensa_integer/evidence/hardware-identification-2026-11-05.txt @@ -0,0 +1,17 @@ +esptool v5.6.0 +Connected to ESP32 on /dev/cu.usbserial-0001: +Chip type: ESP32-D0WD-V3 (revision v3.1) +Features: Wi-Fi, BT, Dual Core + LP Core, 240MHz, Vref calibration in eFuse, Coding Scheme None +Crystal frequency: 40MHz +MAC: [omitted: not required for target qualification] + +Stub flasher running. + +Flash Memory Information: +========================= +Manufacturer: c4 +Device: 6016 +Detected flash size: 4MB +Flash voltage set by a strapping pin: 3.3V + +Hard resetting via RTS pin... From 3b6f7019b02fd7d6a1055f22eebef836fe856647 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 15:39:16 +0000 Subject: [PATCH 27/57] Implement ESP32 eFuse startup reads and Giantec flash profile --- CHANGELOG.md | 14 ++ core/mcu/esp32/efuse.c | 89 +++++++++++ core/mcu/esp32/efuse.h | 27 ++++ core/mcu/esp32/fabric.c | 15 +- core/mcu/esp32/fabric.h | 13 +- core/mcu/esp32/flashmap.c | 25 +-- core/mcu/esp32/flashmap.h | 2 + core/mcu/esp32/gt25q32.c | 11 ++ core/mcu/esp32/gt25q32.h | 25 +++ core/mcu/esp32/machine.c | 11 +- core/mcu/esp32/machine.h | 10 +- core/mcu/esp32/nor32.c | 69 ++++++++ core/mcu/esp32/nor32.h | 23 +++ core/mcu/esp32/w25q32.c | 67 +------- core/mcu/esp32/w25q32.h | 8 +- docs/STATUS.md | 11 +- docs/engineering/esp32-boot-verification.md | 7 + docs/engineering/esp32-dual-core.md | 8 + docs/engineering/esp32-efuse-read-profile.md | 111 +++++++++++++ docs/engineering/esp32-flash-programming.md | 4 + docs/engineering/esp32-giantec-profile.md | 109 +++++++++++++ tests/unit/test_esp32_efuse.c | 82 ++++++++++ tests/unit/test_esp32_gt25q32.c | 65 ++++++++ tests/vendor/README.md | 14 ++ .../evidence/boot-with-efuse-2026-11-05.json | 41 +++++ .../fixtures/efuse-unprogrammed-none.json | 1 + tests/vendor/xtensa_integer/probe_rom_boot.py | 13 +- tests/vendor/xtensa_integer/run_controls.py | 2 +- tests/vendor/xtensa_integer/run_efuse.py | 147 ++++++++++++++++++ tests/vendor/xtensa_integer/run_flash_c.py | 2 +- tests/vendor/xtensa_integer/run_flashmap.py | 2 +- tests/vendor/xtensa_integer/run_ipi_c.py | 2 +- tests/vendor/xtensa_integer/run_machine_c.py | 2 +- tests/vendor/xtensa_integer/run_reset_c.py | 2 +- tests/vendor/xtensa_integer/run_spi_c.py | 26 +++- tests/vendor/xtensa_integer/run_tls_c.py | 2 +- tests/vendor/xtensa_integer/run_w25q32.py | 56 ++++--- 37 files changed, 994 insertions(+), 124 deletions(-) create mode 100644 core/mcu/esp32/efuse.c create mode 100644 core/mcu/esp32/efuse.h create mode 100644 core/mcu/esp32/gt25q32.c create mode 100644 core/mcu/esp32/gt25q32.h create mode 100644 core/mcu/esp32/nor32.c create mode 100644 core/mcu/esp32/nor32.h create mode 100644 docs/engineering/esp32-efuse-read-profile.md create mode 100644 docs/engineering/esp32-giantec-profile.md create mode 100644 tests/unit/test_esp32_efuse.c create mode 100644 tests/unit/test_esp32_gt25q32.c create mode 100644 tests/vendor/xtensa_integer/evidence/boot-with-efuse-2026-11-05.json create mode 100644 tests/vendor/xtensa_integer/fixtures/efuse-unprogrammed-none.json create mode 100644 tests/vendor/xtensa_integer/run_efuse.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b37989f6..0980c725e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Original ESP32 eFuse reads and Giantec storage + +- Added an explicit None-coded 31-word eFuse image/read/reload profile, software + read protection, completion status and source-44 routing. OTP programming, + other coding schemes and hardware fuse consumers remain unqualified. +- A supplied synthetic unprogrammed image advances the real ROM probe from seven + to 30 retired instructions; boot now blocks on RTC control at 0x3ff48000. + This does not establish native SDK boot or reproduce the owner's fuse contents. +- Added a separate Giantec GT25Q32A-U subset matching reported C4 60 16, with + independently qualified maximum-duration and WEL policies. Shared array code + preserves the separate Winbond fixture. Full chip behavior remains unfinished. +- Native/referee/actual compiler checks pass, including 432 firmware-observed + BUSY reads during Giantec programming and 128 C eFuse helper calls. + ### Owner ESP32 hardware identification - Recorded the owner's physical esptool readout: ESP32-D0WD-V3 v3.1, 40 MHz diff --git a/core/mcu/esp32/efuse.c b/core/mcu/esp32/efuse.c new file mode 100644 index 000000000..c4808e594 --- /dev/null +++ b/core/mcu/esp32/efuse.c @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* ESP32 TRM v5.9 chapter 5; ESP-IDF v4.4.7 soc/efuse_reg.h. Original code. */ +#include "mcu/esp32/efuse.h" +#include +void esk_esp32_efuse_init(esk_esp32_efuse *f) +{ + if(!f) return; + memset(f,0,sizeof *f);f->clock=0x4052;f->conf=0x10000;f->dac=40;f->date=0x16042600; +} +static void reload(esk_esp32_efuse *f) +{ + unsigned block;uint32_t disabled=(f->image[0]>>16)&15u; + memcpy(f->read,f->image,sizeof f->read); + for(block=0;block<3;block++) if(disabled&(1u<read+7u+block*8u,0,8*sizeof(uint32_t)); + if(disabled&8u) { f->read[5]&=UINT32_C(0x0fffffff);f->read[6]&=~UINT32_C(0x403); } +} +es_status esk_esp32_efuse_configure(esk_esp32_efuse *f,const uint32_t *words,size_t count) +{ + esk_esp32_efuse next; + if(!f || (!words && count)) return ES_EINVAL; + if(!words && !count) { esk_esp32_efuse_init(f);return ES_OK; } + if(count!=31) return ES_EINVAL; + if((words[6]&3u)!=0 && (words[6]&3u)!=3u) return ES_EUNSUPPORTED; + esk_esp32_efuse_init(&next);memcpy(next.image,words,sizeof next.image);next.enabled=1;reload(&next);*f=next;return ES_OK; +} +/* kind 0=read data, 1=write staging, 2=controller. */ +static es_status decode(const esk_esp32_efuse *f,uint32_t address,unsigned bytes,unsigned *kind,unsigned *index) +{ + unsigned off; + if(address<0x3ff5a000 || address>=0x3ff5b000 || !f->enabled) return ES_EUNSUPPORTED; + if(bytes!=4 || (address&3u)) return ES_EINVAL; + off=address-0x3ff5a000; + if(off<=0x18) { *kind=0;*index=off/4; } + else if(off<=0x34) { *kind=1;*index=(off-0x1c)/4; } + else if(off<=0x94) { *kind=0;*index=7u+(off-0x38)/4; } + else if(off<=0xf4) { *kind=1;*index=7u+(off-0x98)/4; } + else { *kind=2;*index=off; } + return ES_OK; +} +es_status esk_esp32_efuse_read(const esk_esp32_efuse *f,uint32_t address,unsigned bytes,uint32_t *value) +{ + unsigned kind,index;es_status st; + if(!f || !value) return ES_EINVAL; + st=decode(f,address,bytes,&kind,&index);if(st!=ES_OK) return st; + if(kind<2) { *value=kind ? f->write[index] : f->read[index];return ES_OK; } + switch(index) { + case 0xf8:*value=f->clock;break;case 0xfc:*value=f->conf;break; + case 0x100:case 0x104:case 0x11c:*value=0;break; + case 0x108:*value=f->raw;break;case 0x10c:*value=f->raw&f->enable;break; + case 0x110:*value=f->enable;break;case 0x118:*value=f->dac;break;case 0x1fc:*value=f->date;break; + default:return ES_EUNSUPPORTED; + } + return ES_OK; +} +es_status esk_esp32_efuse_write(esk_esp32_efuse *f,uint32_t address,unsigned bytes,uint32_t value) +{ + unsigned kind,index;es_status st; + if(!f) return ES_EINVAL; + st=decode(f,address,bytes,&kind,&index);if(st!=ES_OK) return st; + if(kind==0) return ES_OK; /* TRM software-read registers are RO. */ + if(kind==1) { f->write[index]=value;return ES_OK; } + switch(index) { + case 0xf8:f->clock=value&0x1ffffu;break; + case 0xfc: + /* Only inactive reset/zero and the documented READ mode qualify. + * FORCE_NO_WR_RD_DIS interactions with programming are unqualified. */ + value&=0x1ffffu; + if(value!=0 && value!=0x10000u && value!=0x5aa5u) return ES_EUNSUPPORTED; + f->conf=value;break; + case 0x104: + value&=3u;if(!value) break; + if(value!=1 || f->conf!=0x5aa5u) return ES_EUNSUPPORTED; + reload(f);f->raw|=1;break; + case 0x110:f->enable=value&3u;break; + case 0x114:f->raw&=~(value&3u);break; + case 0x118:if(value&256u) return ES_EUNSUPPORTED;f->dac=value&255u;break; + case 0x1fc:f->date=value;break; + case 0x100:case 0x108:case 0x10c:case 0x11c:break; + default:return ES_EUNSUPPORTED; + } + return ES_OK; +} +es_status esk_esp32_efuse_irq(const esk_esp32_efuse *f,int *asserted) +{ + if(!f || !asserted) return ES_EINVAL; + if(!f->enabled) return ES_EUNSUPPORTED; + *asserted=(f->raw&f->enable)!=0;return ES_OK; +} diff --git a/core/mcu/esp32/efuse.h b/core/mcu/esp32/efuse.h new file mode 100644 index 000000000..b697d1141 --- /dev/null +++ b/core/mcu/esp32/efuse.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_ESP32_EFUSE_H +#define ESK_ESP32_EFUSE_H +#include "embsim/types.h" +/* Opt-in original-ESP32 eFuse read/reload profile. The caller supplies ALL 31 + * uncoded mapped data words (BLK0 seven, BLK1..3 eight each). No factory identity, + * MAC, keys, calibration or security values are invented. Coding None (0/3) + * only; 3/4 and Repeat attach fail. RD_DIS masks protected blocks/fields. + * RDATA is read-only; WDATA stages bits but PGM and write opcodes explicitly + * fail: no burn/OTP, write-protection, ECC, analog timing, supply failure, + * global reset or hidden-hardware consumer qualification. READ_CMD completes + * synchronously in qualified read mode, sets raw completion, and IRQ=raw&enable. + * CLK/DAC fields are configuration storage, not physical waveform timing. + * init/attach is host fixture setup; CPU-only reset does not reset this device. + * All calls serialized, no re-entry. Missing/unselected profile fails explicitly. */ +typedef struct esk_esp32_efuse { + uint32_t image[31],read[31],write[31]; + uint32_t enabled,clock,conf,raw,enable,dac,date; +} esk_esp32_efuse; +void esk_esp32_efuse_init(esk_esp32_efuse *f); +es_status esk_esp32_efuse_configure(esk_esp32_efuse *f,const uint32_t *words,size_t count); +es_status esk_esp32_efuse_read(const esk_esp32_efuse *f,uint32_t address,unsigned bytes,uint32_t *value); +es_status esk_esp32_efuse_write(esk_esp32_efuse *f,uint32_t address,unsigned bytes,uint32_t value); +/* ES_EUNSUPPORTED when profile disabled; output unchanged on failure. */ +es_status esk_esp32_efuse_irq(const esk_esp32_efuse *f,int *asserted); +#endif diff --git a/core/mcu/esp32/fabric.c b/core/mcu/esp32/fabric.c index 0c4d81345..9288e98ee 100644 --- a/core/mcu/esp32/fabric.c +++ b/core/mcu/esp32/fabric.c @@ -3,12 +3,14 @@ /* Address facts: ESP32 TRM v5.9, section 3.3.2. No vendor code copied. */ #include "mcu/esp32/fabric.h" #include "mcu/esp32/flashmap.h" +#include "mcu/esp32/efuse.h" #include struct view { struct esk_esp32_fabric *f;unsigned cpu; }; struct esk_esp32_fabric { esk_alloc *a; unsigned char sram0[0x20000],sram1[0x20000],sram2[0x32000],fast[0x2000],slow[0x2000]; unsigned char *rom[2];esk_esp32_flashmap *flash; + esk_esp32_efuse efuse; unsigned remap[2];struct view view[2]; }; struct location { unsigned char *byte;int readonly,word_only,executable,atomic; }; @@ -59,6 +61,7 @@ static es_status read_bus(void *ctx,uint32_t a,unsigned n,uint32_t *v) { struct view *view=(struct view *)ctx;es_status st; if(!view) return ES_EINVAL; + st=esk_esp32_efuse_read(&view->f->efuse,a,n,v);if(st!=ES_EUNSUPPORTED) return st; if(view->f->flash) { st=esk_esp32_flashmap_reg_read(view->f->flash,a,n,v);if(st!=ES_EUNSUPPORTED) return st; st=esk_esp32_flashmap_read(view->f->flash,view->cpu,a,n,0,v);if(st!=ES_EUNSUPPORTED) return st; @@ -76,6 +79,7 @@ static es_status write_bus(void *ctx,uint32_t a,unsigned n,uint32_t value) { struct view *view=(struct view *)ctx;es_status st; if(!view) return ES_EINVAL; + st=esk_esp32_efuse_write(&view->f->efuse,a,n,value);if(st!=ES_EUNSUPPORTED) return st; if(view->f->flash) { st=esk_esp32_flashmap_reg_write(view->f->flash,a,n,value);if(st!=ES_EUNSUPPORTED) return st; } return access(view,a,n,0,1,&value); } @@ -97,7 +101,7 @@ es_status esk_esp32_fabric_create(esk_alloc *a,esk_esp32_fabric **out) *out=NULL; if(!a) return ES_EINVAL; f=(esk_esp32_fabric *)esk_calloc(a,1,sizeof *f);if(!f) return ES_ENOMEM; - f->a=a;for(i=0;i<2;i++) { f->view[i].f=f;f->view[i].cpu=i; }*out=f;return ES_OK; + f->a=a;esk_esp32_efuse_init(&f->efuse);for(i=0;i<2;i++) { f->view[i].f=f;f->view[i].cpu=i; }*out=f;return ES_OK; } void esk_esp32_fabric_destroy(esk_esp32_fabric *f) { @@ -139,6 +143,11 @@ es_status esk_esp32_fabric_w25q32_configure(esk_esp32_fabric *f,int enabled) if(!f) return ES_EINVAL; return f->flash ? esk_esp32_flashmap_w25q32_configure(f->flash,enabled) : ES_EUNSUPPORTED; } +es_status esk_esp32_fabric_gt25q32_configure(esk_esp32_fabric *f,int enabled) +{ + if(!f) return ES_EINVAL; + return f->flash ? esk_esp32_flashmap_gt25q32_configure(f->flash,enabled) : ES_EUNSUPPORTED; +} es_status esk_esp32_fabric_flash_advance(esk_esp32_fabric *f,uint64_t microseconds) { if(!f) return ES_EINVAL; @@ -149,6 +158,10 @@ es_status esk_esp32_fabric_flash_deadline(const esk_esp32_fabric *f,uint64_t *mi if(!f || !microseconds) return ES_EINVAL; return f->flash ? esk_esp32_flashmap_flash_deadline(f->flash,microseconds) : ES_EUNSUPPORTED; } +es_status esk_esp32_fabric_efuse_configure(esk_esp32_fabric *f,const uint32_t *words,size_t count) +{ return f ? esk_esp32_efuse_configure(&f->efuse,words,count) : ES_EINVAL; } +es_status esk_esp32_fabric_efuse_irq(const esk_esp32_fabric *f,int *asserted) +{ return f ? esk_esp32_efuse_irq(&f->efuse,asserted) : ES_EINVAL; } es_status esk_esp32_fabric_remap(esk_esp32_fabric *f,unsigned cpu,int enabled) { if(!f || cpu>1) return ES_EINVAL; diff --git a/core/mcu/esp32/fabric.h b/core/mcu/esp32/fabric.h index 8fbbe98ac..3a1da7fe0 100644 --- a/core/mcu/esp32/fabric.h +++ b/core/mcu/esp32/fabric.h @@ -9,8 +9,8 @@ typedef struct esk_esp32_fabric esk_esp32_fabric; * non-cache 128K, SRAM1 128K (reversed instruction words), SRAM2 200K, RTC * fast/slow 8K each. RTC fast is PRO-only. Optional read-only flash mapping * and an opt-in SPI1 read path are described below. No full cache/MMU/MPU, - * other peripherals/DMA, RTC power/retention - * or physical reset qualification. RAM starts zero only + * other peripherals/DMA (apart from the explicit eFuse read profile), + * RTC power/retention or physical reset qualification. RAM starts zero only * for deterministic setup. Serialized single-thread calls only; no re-entry. */ es_status esk_esp32_fabric_create(esk_alloc *a,esk_esp32_fabric **out); void esk_esp32_fabric_destroy(esk_esp32_fabric *f); @@ -40,9 +40,18 @@ es_status esk_esp32_fabric_spi1_irq(const esk_esp32_fabric *f,int *asserted); * between machine turns; APP clock gates do not stop this independent clock. * Program/erase require a fresh cache flush after completion before re-enable. */ es_status esk_esp32_fabric_w25q32_configure(esk_esp32_fabric *f,int enabled); +/* Giantec GT25Q32A-U matching JEDEC class; bounded subset in gt25q32.h. + * Same cache/time/attach contract, mutually exclusive with the Winbond fixture. + * No automatic identity selection or claimed package/die revision match. */ +es_status esk_esp32_fabric_gt25q32_configure(esk_esp32_fabric *f,int enabled); es_status esk_esp32_fabric_flash_advance(esk_esp32_fabric *f,uint64_t microseconds); /* Zero deadline means idle; failed calls leave output unchanged. */ es_status esk_esp32_fabric_flash_deadline(const esk_esp32_fabric *f,uint64_t *microseconds); +/* Explicit read-only eFuse image, see efuse.h. All 31 words required, in + * BLK0(7), BLK1(8), BLK2(8), BLK3(8) order. No factory defaults inferred. + * NULL/0 detaches. Host configuration is only allowed between machine turns. */ +es_status esk_esp32_fabric_efuse_configure(esk_esp32_fabric *f,const uint32_t *words,size_t count); +es_status esk_esp32_fabric_efuse_irq(const esk_esp32_fabric *f,int *asserted); /* Host-controlled boot-remap bit, not a DPORT register model. */ es_status esk_esp32_fabric_remap(esk_esp32_fabric *f,unsigned cpu,int enabled); /* All-or-nothing RAM image write through the selected core's address view. */ diff --git a/core/mcu/esp32/flashmap.c b/core/mcu/esp32/flashmap.c index 5fb42252d..24e972b08 100644 --- a/core/mcu/esp32/flashmap.c +++ b/core/mcu/esp32/flashmap.c @@ -5,12 +5,13 @@ #include "mcu/esp32/flashmap.h" #include "mcu/esp32/flashspi.h" #include "mcu/esp32/w25q32.h" +#include "mcu/esp32/gt25q32.h" #include struct esk_esp32_flashmap { esk_alloc *a;unsigned char *image;size_t size; uint32_t table[2][256],ctrl[2],ctrl1[2],dirty[2]; esk_esp32_flashspi spi1;int spi1_enabled; - esk_esp32_w25q32 nor;int nor_enabled; + esk_esp32_nor32 nor;int nor_part; }; es_status esk_esp32_flashmap_create(esk_alloc *a,esk_esp32_flashmap **out) { @@ -35,7 +36,7 @@ es_status esk_esp32_flashmap_install(esk_esp32_flashmap *f,const unsigned char * { unsigned char *copy; if(!f || !data || !len || (len&65535u) || len>0x1000000u) return ES_EINVAL; - if(f->nor_enabled || ((f->ctrl[0]|f->ctrl[1])&8u)) return ES_EUNSUPPORTED; + if(f->nor_part || ((f->ctrl[0]|f->ctrl[1])&8u)) return ES_EUNSUPPORTED; copy=(unsigned char *)esk_malloc(f->a,len);if(!copy) return ES_ENOMEM; memcpy(copy,data,len);if(f->image) esk_free(f->a,f->image,f->size); f->image=copy;f->size=len;f->dirty[0]=f->dirty[1]=1;return ES_OK; @@ -43,30 +44,34 @@ es_status esk_esp32_flashmap_install(esk_esp32_flashmap *f,const unsigned char * static es_status chip_command(void *ctx,unsigned command,int has_address,uint32_t address,const unsigned char *tx,size_t tx_len,unsigned char *rx,size_t rx_len) { esk_esp32_flashmap *f=(esk_esp32_flashmap *)ctx;uint64_t before=f->nor.remaining_us; - es_status st=esk_esp32_w25q32_command(&f->nor,f->image,f->size,command,has_address,address,tx,tx_len,rx,rx_len); + es_status st=f->nor_part==2 ? esk_esp32_gt25q32_command(&f->nor,f->image,f->size,command,has_address,address,tx,tx_len,rx,rx_len) : + esk_esp32_w25q32_command(&f->nor,f->image,f->size,command,has_address,address,tx,tx_len,rx,rx_len); if(st==ES_OK && !before && f->nor.remaining_us) f->dirty[0]=f->dirty[1]=1; return st; } -es_status esk_esp32_flashmap_w25q32_configure(esk_esp32_flashmap *f,int enabled) +static es_status configure_part(esk_esp32_flashmap *f,int part,int enabled) { if(!f) return ES_EINVAL; if(f->size!=0x400000u || ((f->ctrl[0]|f->ctrl[1])&8u) || f->nor.remaining_us) return ES_EUNSUPPORTED; - if(f->nor_enabled!=(enabled!=0)) { esk_esp32_w25q32_init(&f->nor);f->nor_enabled=enabled!=0; } + if(f->nor_part && f->nor_part!=part) return ES_EUNSUPPORTED; + if(f->nor_part!=(enabled ? part : 0)) { esk_esp32_nor32_init(&f->nor);f->nor_part=enabled ? part : 0; } return ES_OK; } +es_status esk_esp32_flashmap_w25q32_configure(esk_esp32_flashmap *f,int enabled) { return configure_part(f,1,enabled); } +es_status esk_esp32_flashmap_gt25q32_configure(esk_esp32_flashmap *f,int enabled) { return configure_part(f,2,enabled); } es_status esk_esp32_flashmap_flash_advance(esk_esp32_flashmap *f,uint64_t microseconds) { uint64_t before;es_status st; if(!f) return ES_EINVAL; - if(!f->nor_enabled) return ES_EUNSUPPORTED; - before=f->nor.remaining_us;st=esk_esp32_w25q32_advance(&f->nor,f->image,f->size,microseconds); + if(!f->nor_part) return ES_EUNSUPPORTED; + before=f->nor.remaining_us;st=esk_esp32_nor32_advance(&f->nor,f->image,f->size,microseconds); if(st==ES_OK && before && !f->nor.remaining_us) f->dirty[0]=f->dirty[1]=1; return st; } es_status esk_esp32_flashmap_flash_deadline(const esk_esp32_flashmap *f,uint64_t *microseconds) { if(!f || !microseconds) return ES_EINVAL; - if(!f->nor_enabled) return ES_EUNSUPPORTED; + if(!f->nor_part) return ES_EUNSUPPORTED; *microseconds=f->nor.remaining_us;return ES_OK; } es_status esk_esp32_flashmap_spi1_configure(esk_esp32_flashmap *f,int enabled) @@ -106,7 +111,7 @@ es_status esk_esp32_flashmap_reg_write(esk_esp32_flashmap *f,uint32_t address,un if(!f) return ES_EINVAL; if(f->spi1_enabled && address>=0x3ff42000 && address<0x3ff42400) { int idle=((f->ctrl[0]|f->ctrl[1])&8u)==0; - return f->nor_enabled ? esk_esp32_flashspi_device_write(&f->spi1,chip_command,f,idle,address,bytes,value) : + return f->nor_part ? esk_esp32_flashspi_device_write(&f->spi1,chip_command,f,idle,address,bytes,value) : esk_esp32_flashspi_write(&f->spi1,f->image,f->size,idle,address,bytes,value); } st=decode(address,bytes,&cpu,&kind,&index);if(st!=ES_OK) return st; @@ -118,7 +123,7 @@ es_status esk_esp32_flashmap_reg_write(esk_esp32_flashmap *f,uint32_t address,un /* Reject unsupported defined RW fields. RO/reserved bits are ignored. */ if(value&UINT32_C(0x10fc4)) return ES_EUNSUPPORTED; if(flush && ((f->ctrl[cpu]|value)&8u)) return ES_EUNSUPPORTED; - if((value&8u) && (f->dirty[cpu] || (f->nor_enabled && f->nor.remaining_us))) return ES_EUNSUPPORTED; + if((value&8u) && (f->dirty[cpu] || (f->nor_part && f->nor.remaining_us))) return ES_EUNSUPPORTED; next=(value&24u)|(f->ctrl[cpu]&32u); if(!(value&16u)) next&=~32u; if(flush) { next|=32u;f->dirty[cpu]=0; } diff --git a/core/mcu/esp32/flashmap.h b/core/mcu/esp32/flashmap.h index d9a9688bb..19a9f0135 100644 --- a/core/mcu/esp32/flashmap.h +++ b/core/mcu/esp32/flashmap.h @@ -26,6 +26,8 @@ es_status esk_esp32_flashmap_spi1_configure(esk_esp32_flashmap *f,int enabled); /* Explicit W25Q32JV-IQ unprotected 4MiB profile, not a board-part assertion. * Both caches disabled; cannot detach or replace backing while busy. */ es_status esk_esp32_flashmap_w25q32_configure(esk_esp32_flashmap *f,int enabled); +/* Giantec subset described by gt25q32.h; mutually exclusive with Winbond. */ +es_status esk_esp32_flashmap_gt25q32_configure(esk_esp32_flashmap *f,int enabled); es_status esk_esp32_flashmap_flash_advance(esk_esp32_flashmap *f,uint64_t microseconds); es_status esk_esp32_flashmap_flash_deadline(const esk_esp32_flashmap *f,uint64_t *microseconds); es_status esk_esp32_flashmap_spi1_irq(const esk_esp32_flashmap *f,int *asserted); diff --git a/core/mcu/esp32/gt25q32.c b/core/mcu/esp32/gt25q32.c new file mode 100644 index 000000000..8258a2b78 --- /dev/null +++ b/core/mcu/esp32/gt25q32.c @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* Giantec GT25Q32A-U V2.5 (June 2026), section 9 and AC tables. Original code. */ +#include "mcu/esp32/gt25q32.h" +static const esk_esp32_nor32_spec spec={{0xc4,0x60,0x16},1500,8000,8000,8000,16000,150,1}; +void esk_esp32_gt25q32_init(esk_esp32_gt25q32 *f) { esk_esp32_nor32_init(f); } +es_status esk_esp32_gt25q32_command(esk_esp32_gt25q32 *f,unsigned char *image,size_t size, + unsigned command,int has_address,uint32_t address,const unsigned char *tx,size_t tx_len,unsigned char *rx,size_t rx_len) +{ return esk_esp32_nor32_command(f,&spec,image,size,command,has_address,address,tx,tx_len,rx,rx_len); } +es_status esk_esp32_gt25q32_advance(esk_esp32_gt25q32 *f,unsigned char *image,size_t size,uint64_t microseconds) +{ return esk_esp32_nor32_advance(f,image,size,microseconds); } diff --git a/core/mcu/esp32/gt25q32.h b/core/mcu/esp32/gt25q32.h new file mode 100644 index 000000000..ef4fd7ab0 --- /dev/null +++ b/core/mcu/esp32/gt25q32.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_ESP32_GT25Q32_H +#define ESK_ESP32_GT25Q32_H +#include "mcu/esp32/nor32.h" +/* Giantec GT25Q32A-U V2.5 unprotected 4MiB standard-SPI subset, matching + * owner's reported C4 60 16 but not authenticating package/suffix/die revision. + * 03/05/06/04/9f/02/20/52/d8/c7/60, PP 1..256 bytes with page wrap/bit clear. + * Uses independently supplied microseconds and datasheet MAXIMUM durations: + * one-byte PP 150us (tBP), longer PP 1500us (tPP), sector/32K/64K + * erase 8000us, chip erase 16000us. WEL clears on + * acceptance as a deterministic early-clear fixture choice within the stated + * unspecified pre-completion interval, not measured WEL timing. Array commits + * at completion; SR1 upper bits explicitly fixture-unprotected zero. + * Not complete: 1KB mini-sector erase (tMSE maximum unspecified in the inspected + * timing tables), fast/dual/quad/QPI, long frames, array-end wrap/aliases, + * protection/status2/3/SFDP/OTP/suspend/reset/power/edges/voltage/endurance/ + * intermediate-cell or power-failure behavior. Unsupported operations fail + * explicitly; busy reads do not invent floating-line data. Serialized only. */ +typedef esk_esp32_nor32 esk_esp32_gt25q32; +void esk_esp32_gt25q32_init(esk_esp32_gt25q32 *f); +es_status esk_esp32_gt25q32_command(esk_esp32_gt25q32 *f,unsigned char *image,size_t size, + unsigned command,int has_address,uint32_t address,const unsigned char *tx,size_t tx_len,unsigned char *rx,size_t rx_len); +es_status esk_esp32_gt25q32_advance(esk_esp32_gt25q32 *f,unsigned char *image,size_t size,uint64_t microseconds); +#endif diff --git a/core/mcu/esp32/machine.c b/core/mcu/esp32/machine.c index 1178b2a14..1bb2c4774 100644 --- a/core/mcu/esp32/machine.c +++ b/core/mcu/esp32/machine.c @@ -7,7 +7,7 @@ struct esk_esp32_machine { esk_alloc *a;esk_esp32_fabric *fabric; esk_xtensa_windows cpu[2];uint64_t retired[2];unsigned running[2],executing; uint32_t app_clock,app_stall,app_boot,app_reset,app_reset_enabled,app_reset_vector,app_reset_pending; - unsigned spi1_owned; + unsigned spi1_owned,efuse_owned; esk_esp32_matrix matrix;struct machine_view view[2]; }; /* Never mutate an executing CPU from a bus callback. Synchronize the @@ -22,6 +22,11 @@ static void sync_inputs(esk_esp32_machine *m) } else if(m->spi1_owned) { (void)esk_esp32_matrix_source(&m->matrix,29,0);m->spi1_owned=0; } + if(esk_esp32_fabric_efuse_irq(m->fabric,&asserted)==ES_OK) { + (void)esk_esp32_matrix_source(&m->matrix,44,asserted);m->efuse_owned=1; + } else if(m->efuse_owned) { + (void)esk_esp32_matrix_source(&m->matrix,44,0);m->efuse_owned=0; + } for(cpu=0;cpu<2;cpu++) if(m->cpu[cpu].irq.enabled && !(cpu==1 && m->app_reset_enabled && m->app_reset)) { uint32_t levels=esk_esp32_matrix_levels(&m->matrix,cpu); for(input=0;input<32;input++) { @@ -107,6 +112,10 @@ es_status esk_esp32_machine_source(esk_esp32_machine *m,unsigned source,int asse if(esk_esp32_fabric_spi1_irq(m->fabric,&asserted_now)==ES_OK) return ES_EUNSUPPORTED; if(m->spi1_owned) sync_inputs(m); } + if(source==44) { + if(esk_esp32_fabric_efuse_irq(m->fabric,&asserted_now)==ES_OK) return ES_EUNSUPPORTED; + if(m->efuse_owned) sync_inputs(m); + } st=esk_esp32_matrix_source(&m->matrix,source,asserted);if(st==ES_OK) sync_inputs(m);return st; } es_status esk_esp32_machine_gpio_irq(esk_esp32_machine *m,unsigned cpu,int nmi,int asserted) diff --git a/core/mcu/esp32/machine.h b/core/mcu/esp32/machine.h index faf23904a..9334e819f 100644 --- a/core/mcu/esp32/machine.h +++ b/core/mcu/esp32/machine.h @@ -8,9 +8,10 @@ typedef struct esk_esp32_machine esk_esp32_machine; /* Experimental two-context execution harness over shared internal memory. * Deterministic serialized instruction turns, not a cycle scheduler or full SoC. - * No vendor boot ROM, peripheral generators, - * cache arbitration/coherence or board model. Host controls are debug/test - * operations, not emulated hardware control registers. No concurrent callers. */ + * No bundled ROM or complete SoC boot. SPI1/eFuse generators are opt-in; + * Other peripherals, cache arbitration/coherence and board model are absent. + * Host configure/run APIs are debug/test operations; firmware APP-control + * registers have the separate contract below. No concurrent callers. */ es_status esk_esp32_machine_create(esk_alloc *a,esk_esp32_machine **out); void esk_esp32_machine_destroy(esk_esp32_machine *m); esk_esp32_fabric *esk_esp32_machine_fabric(esk_esp32_machine *m); @@ -26,7 +27,8 @@ esk_esp32_fabric *esk_esp32_machine_fabric(esk_esp32_machine *m); * not irq_line on CPU peripheral input numbers. Internal timer/software and * profiling inputs remain CPU-local. No API is re-entrant or thread-safe. */ esk_xtensa_memory esk_esp32_machine_bus(esk_esp32_machine *m,unsigned cpu); -/* The opt-in SPI1 read profile owns matrix source 29 while enabled. Its +/* Opt-in SPI1 owns matrix source 29; the eFuse profile owns source 44. + * Both ownerships are active only while their respective profiles are enabled. * completion/enable changes route after commit (immediately for host bus writes). * External source injection into that owned slot fails explicitly. */ es_status esk_esp32_machine_source(esk_esp32_machine *m,unsigned source,int asserted); diff --git a/core/mcu/esp32/nor32.c b/core/mcu/esp32/nor32.c new file mode 100644 index 000000000..43daecc10 --- /dev/null +++ b/core/mcu/esp32/nor32.c @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* Shared 4MiB standard-SPI subset; each named adapter supplies its own + * qualified identity, durations and WEL policy. No vendor code copied. */ +#include "mcu/esp32/nor32.h" +#include +void esk_esp32_nor32_init(esk_esp32_nor32 *f) { if(f) memset(f,0,sizeof *f); } +es_status esk_esp32_nor32_command(esk_esp32_nor32 *f,const esk_esp32_nor32_spec *spec,unsigned char *image,size_t size, + unsigned command,int has_address,uint32_t address,const unsigned char *tx,size_t tx_len,unsigned char *rx,size_t rx_len) +{ + uint64_t duration=0;uint32_t length=0,base=address;size_t i; + if(!f || !spec || !image || size!=0x400000u || (tx_len && !tx) || (rx_len && !rx)) return ES_EINVAL; + /* Validate shape before any storage, WEL or pending-operation change. */ + switch(command) { + case 3: + if(!has_address || tx_len || !rx_len || address>=size || rx_len>size-address) return ES_EUNSUPPORTED; + break; + case 5: + if(has_address || tx_len || !rx_len) return ES_EUNSUPPORTED; + break; + case 0x9f: + if(has_address || tx_len || rx_len!=3) return ES_EUNSUPPORTED; + break; + case 6:case 4: + if(has_address || tx_len || rx_len) return ES_EUNSUPPORTED; + break; + case 2: + if(!has_address || !tx_len || tx_len>256 || rx_len || address>=size) return ES_EUNSUPPORTED; + length=(uint32_t)tx_len;duration=tx_len==1 && spec->program_byte_us ? spec->program_byte_us : spec->program_us;break; + case 0x20:case 0x52:case 0xd8: + if(!has_address || tx_len || rx_len || address>=size) return ES_EUNSUPPORTED; + length=command==0x20 ? 4096u : command==0x52 ? 32768u : 65536u; + base=address&~(length-1u);duration=command==0x20 ? spec->erase4_us : command==0x52 ? spec->erase32_us : spec->erase64_us;break; + case 0xc7:case 0x60: + if(has_address || tx_len || rx_len) return ES_EUNSUPPORTED; + base=0;length=0x400000;duration=spec->erasechip_us;break; + default:return ES_EUNSUPPORTED; + } + if(command==5) { memset(rx,(f->remaining_us ? 1 : 0)|(f->wel ? 2 : 0),rx_len);return ES_OK; } + if(f->remaining_us) { + /* Busy ignores further non-status instructions. Undefined receive-line + * values are not represented by invented pulled-up or retained bytes. */ + return rx_len ? ES_EUNSUPPORTED : ES_OK; + } + if(command==3) { memcpy(rx,image+address,rx_len);return ES_OK; } + if(command==0x9f) { memcpy(rx,spec->jedec,3);return ES_OK; } + if(command==6) { f->wel=1;return ES_OK; } + if(command==4) { f->wel=0;return ES_OK; } + if(!f->wel) return ES_OK; /* Hardware ignores writes without WEL. */ + if(command==2) for(i=0;idata[i]=tx[i]; + if(spec->clear_wel_at_start) f->wel=0; + f->operation=command;f->address=base;f->length=length;f->remaining_us=duration; + return ES_OK; +} +es_status esk_esp32_nor32_advance(esk_esp32_nor32 *f,unsigned char *image,size_t size,uint64_t microseconds) +{ + uint32_t i; + if(!f || !image || size!=0x400000u) return ES_EINVAL; + if(!f->remaining_us) return ES_OK; + if(microsecondsremaining_us) { f->remaining_us-=microseconds;return ES_OK; } + if(f->operation==2) { + uint32_t page=f->address&~UINT32_C(255),offset=f->address&255u; + for(i=0;ilength;i++) { + uint32_t at=page+((offset+i)&255u);image[at]=(unsigned char)(image[at]&f->data[i]); + } + } else memset(image+f->address,255,f->length); + f->remaining_us=0;f->wel=0;f->operation=0;f->address=f->length=0; + return ES_OK; +} diff --git a/core/mcu/esp32/nor32.h b/core/mcu/esp32/nor32.h new file mode 100644 index 000000000..56f4e2ad0 --- /dev/null +++ b/core/mcu/esp32/nor32.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_ESP32_NOR32_H +#define ESK_ESP32_NOR32_H +#include "embsim/types.h" +/* Internal common protocol subset, not a generic qualified flash part. Named + * adapters define identity/timing/WEL semantics; their headers state omissions. */ +typedef struct esk_esp32_nor32 { + uint64_t remaining_us; + uint32_t wel,operation,address,length; + unsigned char data[256]; +} esk_esp32_nor32; +typedef struct esk_esp32_nor32_spec { + unsigned char jedec[3]; + uint64_t program_us,erase4_us,erase32_us,erase64_us,erasechip_us; + uint64_t program_byte_us; /* zero uses the page-program bound */ + int clear_wel_at_start; +} esk_esp32_nor32_spec; +void esk_esp32_nor32_init(esk_esp32_nor32 *f); +es_status esk_esp32_nor32_command(esk_esp32_nor32 *f,const esk_esp32_nor32_spec *spec,unsigned char *image,size_t size, + unsigned command,int has_address,uint32_t address,const unsigned char *tx,size_t tx_len,unsigned char *rx,size_t rx_len); +es_status esk_esp32_nor32_advance(esk_esp32_nor32 *f,unsigned char *image,size_t size,uint64_t microseconds); +#endif diff --git a/core/mcu/esp32/w25q32.c b/core/mcu/esp32/w25q32.c index 123b494da..a28386ca1 100644 --- a/core/mcu/esp32/w25q32.c +++ b/core/mcu/esp32/w25q32.c @@ -1,68 +1,11 @@ /* SPDX-License-Identifier: Apache-2.0 */ /* Copyright 2026 Junior Deogracias */ -/* Winbond W25Q32JV Rev.G (2018-03-27), sections 7/8 and AC timing table. - * Original protocol/state implementation, not vendor implementation code. */ +/* W25Q32JV-IQ Rev.G maximum-duration fixture. Not the owner's board part. */ #include "mcu/esp32/w25q32.h" -#include -void esk_esp32_w25q32_init(esk_esp32_w25q32 *f) { if(f) memset(f,0,sizeof *f); } +static const esk_esp32_nor32_spec spec={{0xef,0x40,0x16},3000,400000,1600000,2000000,50000000,0,0}; +void esk_esp32_w25q32_init(esk_esp32_w25q32 *f) { esk_esp32_nor32_init(f); } es_status esk_esp32_w25q32_command(esk_esp32_w25q32 *f,unsigned char *image,size_t size, unsigned command,int has_address,uint32_t address,const unsigned char *tx,size_t tx_len,unsigned char *rx,size_t rx_len) -{ - uint64_t duration=0;uint32_t length=0,base=address;size_t i; - if(!f || !image || size!=0x400000u || (tx_len && !tx) || (rx_len && !rx)) return ES_EINVAL; - /* Validate shape before any storage, WEL or pending-operation change. */ - switch(command) { - case 3: - if(!has_address || tx_len || !rx_len || address>=size || rx_len>size-address) return ES_EUNSUPPORTED; - break; - case 5: - if(has_address || tx_len || !rx_len) return ES_EUNSUPPORTED; - break; - case 0x9f: - if(has_address || tx_len || rx_len!=3) return ES_EUNSUPPORTED; - break; - case 6:case 4: - if(has_address || tx_len || rx_len) return ES_EUNSUPPORTED; - break; - case 2: - if(!has_address || !tx_len || tx_len>256 || rx_len || address>=size) return ES_EUNSUPPORTED; - length=(uint32_t)tx_len;duration=3000;break; - case 0x20:case 0x52:case 0xd8: - if(!has_address || tx_len || rx_len || address>=size) return ES_EUNSUPPORTED; - length=command==0x20 ? 4096u : command==0x52 ? 32768u : 65536u; - base=address&~(length-1u);duration=command==0x20 ? 400000u : command==0x52 ? 1600000u : 2000000u;break; - case 0xc7:case 0x60: - if(has_address || tx_len || rx_len) return ES_EUNSUPPORTED; - base=0;length=0x400000;duration=50000000;break; - default:return ES_EUNSUPPORTED; - } - if(command==5) { memset(rx,(f->remaining_us ? 1 : 0)|(f->wel ? 2 : 0),rx_len);return ES_OK; } - if(f->remaining_us) { - /* Busy ignores further non-status instructions. Undefined receive-line - * values are not represented by invented pulled-up or retained bytes. */ - return rx_len ? ES_EUNSUPPORTED : ES_OK; - } - if(command==3) { memcpy(rx,image+address,rx_len);return ES_OK; } - if(command==0x9f) { rx[0]=0xef;rx[1]=0x40;rx[2]=0x16;return ES_OK; } - if(command==6) { f->wel=1;return ES_OK; } - if(command==4) { f->wel=0;return ES_OK; } - if(!f->wel) return ES_OK; /* Hardware ignores writes without WEL. */ - if(command==2) for(i=0;idata[i]=tx[i]; - f->operation=command;f->address=base;f->length=length;f->remaining_us=duration; - return ES_OK; -} +{ return esk_esp32_nor32_command(f,&spec,image,size,command,has_address,address,tx,tx_len,rx,rx_len); } es_status esk_esp32_w25q32_advance(esk_esp32_w25q32 *f,unsigned char *image,size_t size,uint64_t microseconds) -{ - uint32_t i; - if(!f || !image || size!=0x400000u) return ES_EINVAL; - if(!f->remaining_us) return ES_OK; - if(microsecondsremaining_us) { f->remaining_us-=microseconds;return ES_OK; } - if(f->operation==2) { - uint32_t page=f->address&~UINT32_C(255),offset=f->address&255u; - for(i=0;ilength;i++) { - uint32_t at=page+((offset+i)&255u);image[at]=(unsigned char)(image[at]&f->data[i]); - } - } else memset(image+f->address,255,f->length); - f->remaining_us=0;f->wel=0;f->operation=0;f->address=f->length=0; - return ES_OK; -} +{ return esk_esp32_nor32_advance(f,image,size,microseconds); } diff --git a/core/mcu/esp32/w25q32.h b/core/mcu/esp32/w25q32.h index c4f45a115..ca946e61e 100644 --- a/core/mcu/esp32/w25q32.h +++ b/core/mcu/esp32/w25q32.h @@ -2,7 +2,7 @@ /* Copyright 2026 Junior Deogracias */ #ifndef ESK_ESP32_W25Q32_H #define ESK_ESP32_W25Q32_H -#include "embsim/types.h" +#include "mcu/esp32/nor32.h" /* Explicit W25Q32JV-IQ unprotected 4MiB storage fixture, NOT identified board * hardware. Qualified standard-SPI subset: 03/05/06/04/9f/02/20/52/d8/c7/60. * PP accepts 1..256 bytes; longer frames and array-end read/address aliases @@ -14,11 +14,7 @@ * follow the command lifecycle. Ignored busy reads have unqualified electrical * output, so fail explicitly instead of fabricating bytes. Caller owns storage * and serializes all calls; initialize before use. No actual-board BOM claim. */ -typedef struct esk_esp32_w25q32 { - uint64_t remaining_us; - uint32_t wel,operation,address,length; - unsigned char data[256]; -} esk_esp32_w25q32; +typedef esk_esp32_nor32 esk_esp32_w25q32; void esk_esp32_w25q32_init(esk_esp32_w25q32 *f); es_status esk_esp32_w25q32_command(esk_esp32_w25q32 *f,unsigned char *image,size_t size, unsigned command,int has_address,uint32_t address,const unsigned char *tx,size_t tx_len,unsigned char *rx,size_t rx_len); diff --git a/docs/STATUS.md b/docs/STATUS.md index 05366dcfe..6bb0fef55 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -7,7 +7,16 @@ does not mean the described behaviour exists. ## Original ESP32 dual-core target investigation (2026-11-04) -**Boot verification, 2026-11-05:** a pinned unmodified ESP-IDF v4.4.7 example +**Current implementation update:** an [explicit eFuse read/reload profile](engineering/esp32-efuse-read-profile.md) +now supports None-coded input images, read protection and completion routing. +With a supplied synthetic unprogrammed image, real ROM execution advances from +seven to 30 retired instructions, then blocks on RTC control at 0x3ff48000. +The owner's actual fuse image is not inferred. A separate +[Giantec GT25Q32A-U storage subset](engineering/esp32-giantec-profile.md) now matches +the reported ID, with independent timing/WEL behavior. Full native boot, +complete chip behavior, OTP programming and SoC hardware consumers remain absent. + +**Initial no-eFuse-image boot verification, 2026-11-05:** a pinned unmodified ESP-IDF v4.4.7 example boots both schedulers and prints Hello world in Espressif QEMU. Native EmbSIM using the same image and QEMU-distributed PRO ROM fails after seven retired instructions: PC 0x4000fdd8, eFuse read 0x3ff5a000. This is a confirmed boot diff --git a/docs/engineering/esp32-boot-verification.md b/docs/engineering/esp32-boot-verification.md index 99a38278f..1bee94c12 100644 --- a/docs/engineering/esp32-boot-verification.md +++ b/docs/engineering/esp32-boot-verification.md @@ -110,6 +110,13 @@ configuration or printed SDK capacity must not substitute for hardware identity. ## Native EmbSIM result: blocked +**Implementation follow-up:** the new [eFuse read profile](esp32-efuse-read-profile.md) +accepts an explicitly supplied image. The synthetic unprogrammed fixture advances +the same ROM to 30 retired instructions and an unimplemented RTC write at +0x3ff48000/PC 0x4000fdcd. That is not the owner's factory image and still does not +boot the application. The baseline no-eFuse-profile result below remains valid. + + The same merged image and Espressif QEMU-distributed PRO ROM were supplied to the native machine. No firmware PC was set past reset and no missing MMIO values were supplied to force boot onward. Result: diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index eed1d376f..03c25d16c 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -26,6 +26,14 @@ manufacturer documentation matches that ID; exact ordering/package remains unresolved. This identifies the measured unit more precisely than the listing, and differs from the separate Winbond test profile. +The [explicit eFuse read/reload profile](esp32-efuse-read-profile.md) now handles +that first access when a caller supplies all 31 uncoded words. A synthetic +unprogrammed fixture advances ROM to 30 retired instructions and the next +unimplemented RTC write at 0x3ff48000. It is not the owner's fuse dump and does +not establish full boot. The [Giantec storage subset](esp32-giantec-profile.md) +adds independently checked timing/WEL behavior for the matching JEDEC family; +complete chip behavior remains unqualified. + ## Reference material Primary references inspected for this investigation: diff --git a/docs/engineering/esp32-efuse-read-profile.md b/docs/engineering/esp32-efuse-read-profile.md new file mode 100644 index 000000000..0d15ae067 --- /dev/null +++ b/docs/engineering/esp32-efuse-read-profile.md @@ -0,0 +1,111 @@ +# ESP32 explicit eFuse image and read/reload profile + +**Implemented: a None-coded eFuse read/reload controller profile with explicit +image input, read protection and completion routing. No OTP programming, complete +eFuse/security integration or native SDK/board boot is claimed.** + +## Architectural contract + +ESP32 TRM v5.9 chapter 5 defines four eFuse blocks, software read latches, write +staging registers, coding/read protection and a read/program command handshake. +This implementation takes all 31 uncoded mapped words explicitly: BLK0 seven, +then BLK1/2/3 eight each. No factory MAC, keys, calibration, security, revision or +voltage values are inferred. Without attachment the MMIO range remains +unsupported. A coding field of 0 or 3 selects None; 3/4 and Repeat are rejected +unchanged. Image attachment copies its inputs and initializes the profile. +Detach is host fixture setup, not a physical eFuse erase. + +RDATA is read-only according to the TRM register table. Some SDK field comments +call individual RDATA fields R/W; this does not override the register-level +software-read contract. RD_DIS bits 16..18 hide BLK1..3. Bit 19 hides block-0 +FLASH_CRYPT_CONFIG, CODING_SCHEME and KEY_STATUS, preserving other fields. +The model keeps the supplied underlying image separate from its protected view. +These inputs are not assertions about a particular unit's unique fuse contents. + +WDATA stores staging words but cannot program the image. The supported handshake +is CONF=0x5aa5 followed by CMD=1: the controller reloads the protected view, +self-clears CMD and latches read-complete RAW bit 0. Completion is synchronous; +physical read/program timing and bus clocks are unqualified. RAW is independent +of ENA; ST and the level IRQ are RAW AND ENA. CLR is write-one-to-clear; reading +that write-only register is unsupported. Source 44 is owned by the machine while +the profile is attached, with synchronization after instruction commit or +immediately after host writes through the machine bus. + +CLK and DAC divider fields hold configuration, without waveform qualification. +DAC pad-clock mode is rejected. CONF supports only inactive/reset and documented +read mode; FORCE_NO_WR_RD_DIS programming interactions are unqualified. WRITE +opcode, PGM_CMD, combined commands and unsupported modes fail unchanged. STATUS +and DEC_STATUS expose the qualified idle/None-coded zero state; no fabricated +ECC/burn success is returned. Only aligned 32-bit MMIO data accesses qualify; +fetch, CAS, narrow access and unknown registers fail. Initial controller fields +come from the pinned register defaults, not assumed chip identity. + +CPU-only APP reset preserves the shared controller image/status/staging within +the existing bounded reset profile. Global reset domains, OTP/write protection, +3/4/repeat encoding, analog programming/supply failure, and direct hardware +consumers of fuse fields (including security, calibration and CPU restrictions) +remain unqualified. The image/read profile does not constitute a full SoC model. + +## Evidence + +`test_esp32_efuse` passes 8,163 assertions across all software-read protection +combinations and both None encodings, all read/staging words, unchanged rejected +images/commands, immutable host input, completion/masking/clear, CPU-only reset, +source ownership and a fetched READ_CMD store followed by level-2 IRQ entry. + +`run_efuse.py` hash-pins ESP-IDF v4.4.7 register, low-level and SoC headers. It +passes 3,968 independent register/protection comparisons. Actual ESP32 compiled +C then executes seven unchanged selected SDK helper bodies through native MMIO: +128 calls, 5,248 retired instructions, on both serialized cores. Register +addresses and bit fields come from the vendor headers. A minimal MMIO macro +fixture supplies bus access, not a vendor controller implementation. Temporary +header/helper extraction retains Espressif's Apache-2.0 notice. Synthetic fuse +images contain no owner's keys, MAC or calibration. This is selected helper +acceptance, not the full burn stack or SDK boot. + +## Real ROM probe and next blocker + +The previous no-image probe still stops at eFuse after seven retired instructions. +With the explicitly selected +[unprogrammed None-coded test image](../../tests/vendor/xtensa_integer/fixtures/efuse-unprogrammed-none.json), +the same QEMU-distributed PRO ROM and actual ESP-IDF flash image now reach: + +```text +retired instructions 30 +failure PC 0x4000fdcd +failure address 0x3ff48000 +fault store failure, RTC_CNTL_OPTIONS0_REG +``` + +The [recorded probe](../../tests/vendor/xtensa_integer/evidence/boot-with-efuse-2026-11-05.json) +is a **blocked boot**, not application success. The test fuse image is deliberately +unprogrammed; it is neither a dump of the owner's v3.1 unit nor a claim about +QEMU's full factory image. The ROM itself consumes the controller data. There +are no unimplemented-MMIO fallback values or host PC shortcuts. PRID/reset/DROM +tail fixture limitations from the [boot investigation](esp32-boot-verification.md) +still apply. RTC/power/reset and subsequent boot requirements remain unfinished. + +## References and reproduction + +ESP-IDF v4.4.7 external inputs: + +- `components/soc/esp32/include/soc/efuse_reg.h`, SHA-256 + `cfe37e8e692a22e41885f606bee7c4c12e0308abbdeb5f13f6cc92471453ff03`. +- `components/hal/esp32/include/hal/efuse_ll.h`, SHA-256 + `ce3dfed567383582c6ced10a9edc08de296c5b5d541ee171efdd07b0014e3f30`. +- `soc.h` and actual ESP32 toolchain pins are in the target investigation. + +```sh +python3 tests/vendor/xtensa_integer/run_efuse.py \ + --efuse-reg /path/to/efuse_reg.h --efuse-ll /path/to/efuse_ll.h \ + --soc-header /path/to/soc.h \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld --nm /path/to/xtensa-esp32-elf-nm +python3 tests/vendor/xtensa_integer/probe_rom_boot.py \ + --rom /path/to/esp32-v3-rom.bin --flash /path/to/idf-hello-flash.bin \ + --efuse tests/vendor/xtensa_integer/fixtures/efuse-unprogrammed-none.json +``` + +The boot diagnostic returns 1 for blocked/inconclusive execution. It is not a +passing conformance test. Runtime code and fixtures do not embed vendor ROM. diff --git a/docs/engineering/esp32-flash-programming.md b/docs/engineering/esp32-flash-programming.md index 95f30b110..dd958a88a 100644 --- a/docs/engineering/esp32-flash-programming.md +++ b/docs/engineering/esp32-flash-programming.md @@ -22,6 +22,10 @@ That differs from this fixture's EF 40 16. See the [physical readout evidence](esp32-boot-verification.md). Selecting this Winbond part is a separate test configuration, not the owner's verified board profile. +The matching family now has a separate [Giantec subset](esp32-giantec-profile.md), +with its own timing and WEL semantics. It shares only the qualified common array +engine with this adapter; Winbond identity/behavior is not substituted for Giantec. + ## Qualified subset `w25q32.c` is an original protocol/state implementation with no ESP32 register diff --git a/docs/engineering/esp32-giantec-profile.md b/docs/engineering/esp32-giantec-profile.md new file mode 100644 index 000000000..2b050db89 --- /dev/null +++ b/docs/engineering/esp32-giantec-profile.md @@ -0,0 +1,109 @@ +# Explicit Giantec GT25Q32A-U storage profile + +**Implemented: bounded unprotected standard-SPI programming/read/erase for the +Giantec family matching the owner's reported C4 60 16 ID. Not a complete chip, +exact ordering/die revision match, physical timing or native ROM/SDK boot.** + +## Part-specific behavior + +Giantec GT25Q32A-U V2.5 (June 2026) section 9.2 identifies C4 60 16, matching the +[owner's esptool report](esp32-boot-verification.md). The model has a separate +named adapter, not a renamed Winbond device. Only the shared, verified standard +array protocol is factored into `nor32.c`; identities, durations and WEL policies +are independently selected by each named adapter. Winbond's existing fixture and +behavior remain available and separately tested. + +The qualified Giantec subset is 03h read, 05h status1, 06h/04h WREN/WRDI, 9Fh +ID, 02h page program and 20h/52h/D8h/C7h/60h erase. Storage is an explicitly +supplied 4 MiB image; the model does not invent missing/erased bytes. PP accepts +1..256 bytes, wraps within its 256-byte page and clears bits. Writes without WEL +and supported no-receive commands while BUSY are ignored. Data/ID reads during +BUSY fail because the ignored receive-line output is unqualified. SR1 upper bits +are the explicit unprotected fixture configuration, not a measured board state. + +The AC tables give these maximum-duration fixture bounds: + +| Operation | Giantec selected bound | +|---|---| +| One-byte PP (tBP) | 150 us | +| Longer page program (tPP) | 1,500 us | +| 4 KiB, 32 KiB, 64 KiB erase | 8,000 us | +| Chip erase | 16,000 us | + +These differ substantially from the Winbond profile. They are conservative +published bounds, not measured times or proof that the owner's flash die uses +this exact revision. Program/erase BUSY clears at the supplied deadline; array +changes commit then. External microseconds advance independently of CPU turns +or APP gating. Exact physical timing, intermediate cells and power failure are +unqualified. + +Giantec specifies that WEL resets at an **unspecified time before completion**. +The model chooses early clear on accepted program/erase as a deterministic +fixture policy inside that unspecified interval; no measured WEL transition time +is claimed. Winbond's separately specified completion-time clear is preserved. +Status-polling firmware must use BUSY for operation completion. + +## Attachment and omissions + +`esk_esp32_fabric_gt25q32_configure` explicitly attaches the named part. It +requires a 4 MiB backing, both caches disabled and no pending operation. Named +parts are mutually exclusive: attaching/detaching the wrong active part fails, +rather than silently changing identity. Attach/detach resets chip fixture state, +not storage/controller state; this is host setup, not a silicon power action. +Backing replacement while attached fails. CPU-only reset preserves the external +chip operation. SPI1 transactions use the existing validated byte/buffer phases; +each transfer is limited to 64 bytes, or 32 in a selected high buffer. Firmware +must split at page and controller-buffer boundaries. + +The existing conservative cache contract also applies: both caches disabled +throughout SPI1 transfers, no re-enable while BUSY, and a fresh flush after +completion before mapped reads. It is not live cache-tag/coherence or SPI0/1 +arbitration qualification. Transaction completion/source 29 is distinct from +self-timed chip completion. + +Unimplemented features fail explicitly: 1 KiB mini-sector erase 82h (the inspected +AC tables do not specify its tMSE maximum), fast/dual/quad/QPI, array-end read +wrap/address aliases, long PP frames, status2/3/protection writes, SFDP, OTP, +suspend/resume, reset/power-down, pin/voltage/drive behavior and endurance/disturb. +No unsupported protection write is accepted while protecting nothing. This +subset must not be presented as every GT25Q32A-U feature or exact board fidelity. + +## Evidence + +- `test_esp32_gt25q32`: 1,172 native assertions covering identity, byte/page bounds, + WEL policy, all qualified erase sizes/aliases, buffer packing, busy detach and + mutual exclusion. The shared engine's all-page-offset/bit-clearing tests also + pass through the unchanged Winbond adapter. +- `run_w25q32.py --part gt25q32` independently parses the pinned Giantec PDF's ID, + tBP/tPP/erase maxima and checks a Python array/time oracle: 2,631 comparisons, + 219 completed operations and 37 ignored writes. The separate Winbond PDF + referee continues to pass. +- Actual ESP32 C plus seven unchanged SDK helper bodies performs WREN, page-safe + PP, status polling, readback and execution of supplied code copied to RAM. + With Giantec selected and a deliberately slower external-time fixture: + **128 reads, 32 programmed workers, 32 exact replays, 139,520 retired + instructions and 432 firmware-observed BUSY reads** pass. Native status/lock + memory is observed directly; there are no Python memory callbacks or host + worker-PC shortcuts. The fixture injects 1 ms every 257 scheduled turns, + rather than claiming an instruction-to-time conversion. It explicitly checks + that firmware consumes BUSY=1 before completion. + +## References and reproduction + +Primary reference: [Giantec GT25Q32A-U V2.5](https://en.giantec-semi.com/uploads/pdf/260605/GT25Q32A%20U%20DS_V2.5.pdf), +sections 8/9 and AC characteristics, inspected 2026-11-05. SHA-256: +`df021758e55bde131f39af59c42acac20f2e0b32e251f4ade673687d22b7cd55`. +The datasheet and SDK inputs remain external, not vendored implementation. + +```sh +python3 tests/vendor/xtensa_integer/run_w25q32.py --part gt25q32 \ + --datasheet /path/to/GT25Q32A-U-V2.5.pdf +python3 tests/vendor/xtensa_integer/run_spi_c.py --program-workers \ + --flash-part gt25q32 --flash-tick-period 257 \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld --nm /path/to/xtensa-esp32-elf-nm \ + --ll-header /path/to/spi_flash_ll.h --struct-header /path/to/spi_struct.h +``` + +Toolchain and SDK hashes are in the [SPI1 contract](esp32-spi1-reads.md). diff --git a/tests/unit/test_esp32_efuse.c b/tests/unit/test_esp32_efuse.c new file mode 100644 index 000000000..4ae4f7c87 --- /dev/null +++ b/tests/unit/test_esp32_efuse.c @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/efuse.h" +#include "mcu/esp32/machine.h" +static uint32_t data_reg(unsigned i) { return 0x3ff5a000u+(i<7 ? i*4u : 0x38u+(i-7u)*4u); } +static uint32_t stage_reg(unsigned i) { return 0x3ff5a000u+(i<7 ? 0x1cu+i*4u : 0x98u+(i-7u)*4u); } +static void supplied_images_and_protection(void) +{ + esk_esp32_efuse f,before;uint32_t image[31],value=0;unsigned i,mask,scheme;int irq=77; + esk_esp32_efuse_init(&f);CHECK_EQ(esk_esp32_efuse_irq(&f,&irq),ES_EUNSUPPORTED);CHECK_EQ(irq,77); + CHECK_EQ(esk_esp32_efuse_read(&f,data_reg(0),4,&value),ES_EUNSUPPORTED); + for(mask=0;mask<16;mask++) for(scheme=0;scheme<4;scheme+=3) { + for(i=0;i<31;i++) image[i]=0xf1230000u+i; + image[0]=(image[0]&~UINT32_C(0xf0000))|(mask<<16);image[6]=0xfffffffcu|scheme; + CHECK_EQ(esk_esp32_efuse_configure(&f,image,31),ES_OK); + for(i=0;i<31;i++) { + uint32_t wanted=image[i]; + if(i>=7 && (mask&(1u<<((i-7u)/8u)))) wanted=0; + if((mask&8u) && i==5) wanted&=0xfffffff; + if((mask&8u) && i==6) wanted&=~UINT32_C(0x403); + CHECK_EQ(esk_esp32_efuse_read(&f,data_reg(i),4,&value),ES_OK);CHECK_EQ(value,wanted); + CHECK_EQ(esk_esp32_efuse_write(&f,data_reg(i),4,0),ES_OK); + CHECK_EQ(esk_esp32_efuse_read(&f,data_reg(i),4,&value),ES_OK);CHECK_EQ(value,wanted); + CHECK_EQ(esk_esp32_efuse_write(&f,stage_reg(i),4,~image[i]),ES_OK); + CHECK_EQ(esk_esp32_efuse_read(&f,stage_reg(i),4,&value),ES_OK);CHECK_EQ(value,~image[i]); + } + before=f;image[6]=1;CHECK_EQ(esk_esp32_efuse_configure(&f,image,31),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&f,&before,sizeof f),0); + image[6]=2;CHECK_EQ(esk_esp32_efuse_configure(&f,image,31),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&f,&before,sizeof f),0); + } +} +static void reload_interrupt_and_failures(void) +{ + esk_esp32_efuse f,before;uint32_t image[31],value=0;unsigned i;int irq=0; + for(i=0;i<31;i++) image[i]=0x12340000u+i; + image[6]&=~3u; + esk_esp32_efuse_init(&f);CHECK_EQ(esk_esp32_efuse_configure(&f,image,31),ES_OK);image[1]=0; + CHECK_EQ(esk_esp32_efuse_read(&f,data_reg(1),4,&value),ES_OK);CHECK_EQ(value,0x12340001); + before=f;CHECK_EQ(esk_esp32_efuse_write(&f,0x3ff5a104,4,1),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&f,&before,sizeof f),0); + CHECK_EQ(esk_esp32_efuse_write(&f,0x3ff5a0fc,4,0x5aa5),ES_OK); + CHECK_EQ(esk_esp32_efuse_write(&f,0x3ff5a104,4,1),ES_OK); + CHECK_EQ(esk_esp32_efuse_read(&f,0x3ff5a108,4,&value),ES_OK);CHECK_EQ(value,1); + CHECK_EQ(esk_esp32_efuse_read(&f,0x3ff5a10c,4,&value),ES_OK);CHECK_EQ(value,0); + CHECK_EQ(esk_esp32_efuse_write(&f,0x3ff5a110,4,1),ES_OK);CHECK_EQ(esk_esp32_efuse_irq(&f,&irq),ES_OK);CHECK_EQ(irq,1); + CHECK_EQ(esk_esp32_efuse_write(&f,0x3ff5a114,4,1),ES_OK);CHECK_EQ(esk_esp32_efuse_irq(&f,&irq),ES_OK);CHECK_EQ(irq,0); + CHECK_EQ(esk_esp32_efuse_read(&f,data_reg(1),4,&value),ES_OK);CHECK_EQ(value,0x12340001); + before=f;CHECK_EQ(esk_esp32_efuse_write(&f,0x3ff5a0fc,4,0x5a5a),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&f,&before,sizeof f),0); + CHECK_EQ(esk_esp32_efuse_write(&f,0x3ff5a104,4,2),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&f,&before,sizeof f),0); + CHECK_EQ(esk_esp32_efuse_write(&f,0x3ff5a104,4,3),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&f,&before,sizeof f),0); + CHECK_EQ(esk_esp32_efuse_configure(&f,image,30),ES_EINVAL);CHECK_EQ(memcmp(&f,&before,sizeof f),0); + CHECK_EQ(esk_esp32_efuse_read(&f,0x3ff5a120,4,&value),ES_EUNSUPPORTED); + value=0xdeadbeef;CHECK_EQ(esk_esp32_efuse_read(&f,0x3ff5a114,4,&value),ES_EUNSUPPORTED);CHECK_EQ(value,0xdeadbeefu); + CHECK_EQ(esk_esp32_efuse_read(&f,data_reg(1),1,&value),ES_EINVAL);CHECK_EQ(value,0xdeadbeefu); + CHECK_EQ(esk_esp32_efuse_configure(&f,NULL,0),ES_OK);CHECK_EQ(esk_esp32_efuse_read(&f,data_reg(1),4,&value),ES_EUNSUPPORTED); +} +static void native_bus_and_reset_retention(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_xtensa_memory bus;esk_esp32_fabric *f;uint32_t image[31]={0},value=0;int irq; + const unsigned char code[]={0x32,0x62,0};esk_xtensa_windows *w;unsigned event;esk_xtensa_fault fault; + image[1]=0x12345678;esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK);f=esk_esp32_machine_fabric(m); + CHECK_EQ(esk_esp32_fabric_efuse_configure(f,image,31),ES_OK);bus=esk_esp32_machine_bus(m,0); + CHECK_EQ(bus.read(bus.ctx,data_reg(1),4,&value),ES_OK);CHECK_EQ(value,0x12345678); + CHECK_EQ(bus.fetch(bus.ctx,data_reg(1),3,&value),ES_EUNSUPPORTED); + CHECK_EQ(bus.compare_exchange(bus.ctx,data_reg(1),0,0,&value),ES_EUNSUPPORTED); + CHECK_EQ(esk_esp32_machine_source(m,44,1),ES_EUNSUPPORTED); + CHECK_EQ(bus.write(bus.ctx,0x3ff001b4,4,19),ES_OK);CHECK_EQ(bus.write(bus.ctx,0x3ff5a110,4,1),ES_OK); + CHECK_EQ(bus.write(bus.ctx,0x3ff5a0fc,4,0x5aa5),ES_OK); + CHECK_EQ(esk_esp32_fabric_load(f,0,0x40080000,code,sizeof code),ES_OK); + CHECK_EQ(esk_esp32_machine_configure(m,0,0x40080000),ES_OK);w=esk_esp32_machine_core(m,0); + w->irq.enabled=1;w->irq.intenable=1u<<19;w->irq.vectors[2]=0x40084000;w->cpu.a[2]=0x3ff5a104;w->cpu.a[3]=1; + CHECK_EQ(esk_esp32_machine_run_core(m,0,1),ES_OK);CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK); + CHECK_EQ(event,0);CHECK_EQ(w->cpu.pc,0x40080003);CHECK(w->irq.lines&(1u<<19)); + CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK);CHECK_EQ(event,12);CHECK_EQ(w->irq.epc[2],0x40080003); + CHECK_EQ(esk_esp32_machine_app_reset_configure(m,1),ES_OK);CHECK_EQ(esk_esp32_fabric_efuse_irq(f,&irq),ES_OK);CHECK_EQ(irq,1); + CHECK_EQ(bus.read(bus.ctx,data_reg(1),4,&value),ES_OK);CHECK_EQ(value,0x12345678); + CHECK_EQ(bus.write(bus.ctx,0x3ff5a114,4,1),ES_OK);CHECK(!(w->irq.lines&(1u<<19))); + CHECK_EQ(esk_esp32_fabric_efuse_configure(f,NULL,0),ES_OK);CHECK_EQ(esk_esp32_machine_source(m,44,1),ES_OK); + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +TEST_MAIN_BEGIN + (void)t_str_eq;RUN(supplied_images_and_protection);RUN(reload_interrupt_and_failures);RUN(native_bus_and_reset_retention); +TEST_MAIN_END diff --git a/tests/unit/test_esp32_gt25q32.c b/tests/unit/test_esp32_gt25q32.c new file mode 100644 index 000000000..15c1e8908 --- /dev/null +++ b/tests/unit/test_esp32_gt25q32.c @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/gt25q32.h" +#include "mcu/esp32/fabric.h" +static es_status command(esk_esp32_gt25q32 *f,unsigned char *image,unsigned cmd,int has,uint32_t address,const unsigned char *tx,size_t n,unsigned char *rx,size_t m) +{ return esk_esp32_gt25q32_command(f,image,0x400000,cmd,has,address,tx,n,rx,m); } +static void identity_program_timing_and_erase(void) +{ + esk_esp32_gt25q32 f,before;unsigned char *image=(unsigned char *)malloc(0x400000);unsigned char tx[64],rx[3]; + const unsigned lengths[]={1,2,16,64},cmds[]={0x20,0x52,0xd8,0xc7,0x60},sizes[]={4096,32768,65536,0x400000,0x400000}; + unsigned k,i;CHECK(image!=NULL);if(!image) return; + memset(image,255,0x400000);esk_esp32_gt25q32_init(&f); + CHECK_EQ(command(&f,image,0x9f,0,0,NULL,0,rx,3),ES_OK);CHECK_EQ(rx[0],0xc4);CHECK_EQ(rx[1],0x60);CHECK_EQ(rx[2],0x16); + for(k=0;k<4;k++) { + uint64_t duration=lengths[k]==1 ? 150u : 1500u; + memset(image+0x100,255,256);for(i=0;i<64;i++) tx[i]=(unsigned char)(0xf0u^(i&255u)); + CHECK_EQ(command(&f,image,6,0,0,NULL,0,NULL,0),ES_OK); + CHECK_EQ(command(&f,image,2,1,0x1ff,tx,lengths[k],NULL,0),ES_OK);CHECK_EQ(f.remaining_us,duration);CHECK_EQ(f.wel,0); + CHECK_EQ(command(&f,image,5,0,0,NULL,0,rx,3),ES_OK);CHECK_EQ(rx[0],1);CHECK_EQ(rx[2],1); + CHECK_EQ(esk_esp32_gt25q32_advance(&f,image,0x400000,duration-1),ES_OK);CHECK_EQ(image[0x1ff],255); + CHECK_EQ(esk_esp32_gt25q32_advance(&f,image,0x400000,1),ES_OK);CHECK_EQ(f.remaining_us,0); + for(i=0;i<256;i++) { unsigned source=(i+1u)&255u;CHECK_EQ(image[0x100u+i],source>shift)&mask + reads=[address(f'EFUSE_BLK{block}_RDATA{word}_REG') for block in range(4) for word in range(7 if block==0 else 8)] + writes=[address(f'EFUSE_BLK{block}_WDATA{word}_REG') for block in range(4) for word in range(7 if block==0 else 8)] + helpers=[] + for name in ['get_flash_crypt_cnt','get_mac0','get_mac1','get_coding_scheme','get_cmd','set_read_cmd','set_conf_read_op_code']: + match=re.search(r'^__attribute__[^\n]*\befuse_ll_'+name+r'\([^\n]*\)\n\{',ll,re.M);assert match,name + end=match.end();depth=1 + while depth: + if ll[end]=='{':depth+=1 + elif ll[end]=='}':depth-=1 + end+=1 + helpers.append(ll[match.start():end]) + images=[];visible=[] + for trial in range(64): + words=[rng.getrandbits(32) for _ in range(31)];words[6]=(words[6]&~3)|(3 if trial&16 else 0) + words[0]=(words[0]&~0xf0000)|((trial%16)<<16);expected=list(words) + disabled=get(words[0],'RD_EFUSE_RD_DIS') + for block in range(1,4): + if disabled&(1<<(block-1)):expected[7+(block-1)*8:7+block*8]=[0]*8 + if disabled&8: + for index,name in [(5,'RD_FLASH_CRYPT_CONFIG'),(6,'RD_KEY_STATUS'),(6,'RD_CODING_SCHEME')]: + mask,shift=field(name);expected[index]&=~(mask<\n#define DR_REG_EFUSE_BASE '+hex(base)+'''\n#define BIT(n) (1U<<(n)) +#define REG_READ(r) (*(volatile uint32_t *)(r)) +#define REG_WRITE(r,v) (*(volatile uint32_t *)(r)=(v)) +#define REG_GET_FIELD(r,f) ((REG_READ(r)>>f##_S)&f##_V) +''',encoding='utf-8') + source=ll[:ll.index('#pragma once')]+'\n#include \n#include \n#include "efuse_reg.h"\n'+'\n'.join(helpers)+''' +unsigned inspect(unsigned *out) { + efuse_ll_set_conf_read_op_code(); efuse_ll_set_read_cmd(); + while(efuse_ll_get_cmd()) { } + out[0]=efuse_ll_get_flash_crypt_cnt();out[1]=efuse_ll_get_mac0(); + out[2]=efuse_ll_get_mac1();out[3]=efuse_ll_get_coding_scheme(); + out[4]=REG_READ(EFUSE_INT_RAW_REG); + return out[0]^out[1]^out[2]^out[3]^out[4]; +} +''' + src=out/'inspect.c';obj=out/'inspect.o';elf=out/'inspect.elf';binary=out/'inspect.bin';src.write_text(source,encoding='utf-8') + subprocess.run([args.compiler,'-B'+str(Path(args.compiler).parent)+'/', '-O1','-ffreestanding','-fno-builtin','-mtext-section-literals','-c',str(src),'-o',str(obj)],check=True) + subprocess.run([args.linker,'--no-relax','-Ttext=0x40080000','--entry=inspect',str(obj),'-o',str(elf)],check=True) + subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(elf),str(binary)],check=True) + symbols={f[2]:int(f[0],16) for line in subprocess.check_output([args.nm,str(elf)],text=True).splitlines() if len(f:=line.split())==3} + ai=bind('esk_alloc_init',None,C.POINTER(Allocator),pointer);create=bind('esk_esp32_machine_create',C.c_int,C.POINTER(Allocator),C.POINTER(pointer));destroy=bind('esk_esp32_machine_destroy',None,pointer) + fabric=bind('esk_esp32_machine_fabric',pointer,pointer);config=bind('esk_esp32_fabric_efuse_configure',C.c_int,pointer,C.POINTER(C.c_uint32),C.c_size_t) + load=bind('esk_esp32_fabric_load',C.c_int,pointer,C.c_uint,C.c_uint32,pointer,C.c_size_t);bus_get=bind('esk_esp32_machine_bus',Memory,pointer,C.c_uint) + configure=bind('esk_esp32_machine_configure',C.c_int,pointer,C.c_uint,C.c_uint32);core=bind('esk_esp32_machine_core',C.POINTER(Windows),pointer,C.c_uint) + run=bind('esk_esp32_machine_run_core',C.c_int,pointer,C.c_uint,C.c_int);step=bind('esk_esp32_machine_step',C.c_int,pointer,C.c_uint,C.POINTER(C.c_uint),C.POINTER(Fault)) + total=0;calls=0;code=binary.read_bytes() + for words,expected in zip(images,visible): + allocator=Allocator();ai(C.byref(allocator),None);m=pointer();assert create(C.byref(allocator),C.byref(m))==0 + try: + ram=fabric(m);raw=(C.c_uint32*31)(*words);assert config(ram,raw,31)==0 + assert load(ram,0,0x40080000,C.c_char_p(code),len(code))==0 + bus=bus_get(m,0);assert bus.write(bus.ctx,0x3ff00030,4,1)==0 + states=[] + for cpu in range(2): + assert configure(m,cpu,symbols['inspect'])==0 + w=core(m,cpu).contents;w.ps|=1<<16;w.cpu.a[1]=0x3ffd0000+cpu*4096;w.cpu.a[4]=0x7ffffff0;w.cpu.a[6]=0x3ffb9000+cpu*64 + states.append(w);assert run(m,cpu,1)==0 + done=[False,False];event=C.c_uint();fault=Fault() + for turn in range(2000): + cpu=turn%2;w=states[cpu] + if done[cpu]:continue + if w.cpu.pc==0x7ffffff0: + done[cpu]=True + if all(done):break + continue + status=step(m,cpu,C.byref(event),C.byref(fault));assert status==0 and event.value==0,(status,hex(fault.pc),hex(fault.opcode),event.value) + total+=1 + assert all(done) + wanted=[get(expected[0],'RD_FLASH_CRYPT_CNT'),expected[1],get(expected[2],'RD_WIFI_MAC_CRC_HIGH')&65535,get(expected[6],'RD_CODING_SCHEME'),1] + result=0 + for item in wanted:result^=item + for cpu,w in enumerate(states): + for i,item in enumerate(wanted):assert bus.read(bus.ctx,0x3ffb9000+cpu*64+i*4,4,C.byref(value))==0 and value.value==item + assert w.cpu.a[6]==result and w.base==0 and w.start==1 and w.cpu.a[1]==0x3ffd0000+cpu*4096;calls+=1 + finally:destroy(m) + assert allocator.live_bytes==0 + print(f'{comparisons} pinned eFuse register/protection comparisons; {calls} actual ESP32 C calls, {total} retired instructions, seven unchanged SDK helper bodies') + + +if __name__=='__main__':main() diff --git a/tests/vendor/xtensa_integer/run_flash_c.py b/tests/vendor/xtensa_integer/run_flash_c.py index cedf1e1dc..5e50fcd79 100644 --- a/tests/vendor/xtensa_integer/run_flash_c.py +++ b/tests/vendor/xtensa_integer/run_flash_c.py @@ -68,7 +68,7 @@ def compile_code(name, source, base, entry, flags=()): flash_image = bytes(image) lib = out / 'flash.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_flashmap.py b/tests/vendor/xtensa_integer/run_flashmap.py index 915cb03fb..a1066e71c 100644 --- a/tests/vendor/xtensa_integer/run_flashmap.py +++ b/tests/vendor/xtensa_integer/run_flashmap.py @@ -51,7 +51,7 @@ def initial(name): with tempfile.TemporaryDirectory(prefix='embsim-flashmap-') as tmp: lib = Path(tmp) / 'flashmap.so' subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', - '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), str(ROOT / 'core/mcu/esp32/flashmap.c'), str(ROOT / 'core/mcu/esp32/flashspi.c'), str(ROOT / 'core/mcu/esp32/w25q32.c'), + '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), str(ROOT / 'core/mcu/esp32/flashmap.c'), str(ROOT / 'core/mcu/esp32/flashspi.c'), str(ROOT / 'core/mcu/esp32/w25q32.c'), str(ROOT / 'core/mcu/esp32/nor32.c'), str(ROOT / 'core/mcu/esp32/gt25q32.c'), str(ROOT / 'core/util/alloc.c'), '-o', str(lib)], check=True) api = C.CDLL(str(lib)); pointer = C.c_void_p def bind(name, result, *params): diff --git a/tests/vendor/xtensa_integer/run_ipi_c.py b/tests/vendor/xtensa_integer/run_ipi_c.py index 5b979b0d8..75f82f8da 100644 --- a/tests/vendor/xtensa_integer/run_ipi_c.py +++ b/tests/vendor/xtensa_integer/run_ipi_c.py @@ -92,7 +92,7 @@ def main(): if len(f := line.split()) == 3} lib = out / 'ipi.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_machine_c.py b/tests/vendor/xtensa_integer/run_machine_c.py index e21bd38f1..956b643ad 100644 --- a/tests/vendor/xtensa_integer/run_machine_c.py +++ b/tests/vendor/xtensa_integer/run_machine_c.py @@ -59,7 +59,7 @@ def main(): if len(f := line.split()) == 3} lib = out / 'machine.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), diff --git a/tests/vendor/xtensa_integer/run_reset_c.py b/tests/vendor/xtensa_integer/run_reset_c.py index 90eca177d..d0b74c72d 100644 --- a/tests/vendor/xtensa_integer/run_reset_c.py +++ b/tests/vendor/xtensa_integer/run_reset_c.py @@ -117,7 +117,7 @@ def compile_image(name, source, base, entry, assembly=False): rom = rom + bytes(0x60000 - len(rom)) # original test fixture, not a product ROM lib = out / 'reset.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_spi_c.py b/tests/vendor/xtensa_integer/run_spi_c.py index 19975fefe..3b4a71fdb 100644 --- a/tests/vendor/xtensa_integer/run_spi_c.py +++ b/tests/vendor/xtensa_integer/run_spi_c.py @@ -6,7 +6,7 @@ Both cores serialize the shared controller with a compiled atomic lock. Some runs copy code from SPI into instruction RAM and call it. No vendor ROM/SDK boot, DMA, physical timing or complete board qualification. --program-workers uses -the explicitly selected unprotected W25Q32JV-IQ subset, not a board identity. +an explicitly selected unprotected named-part subset, not a full board identity. """ import argparse import ctypes as C @@ -93,7 +93,11 @@ def main(): for name in ['compiler','objcopy','linker','nm']: ap.add_argument('--'+name,required=True) ap.add_argument('--ll-header',type=Path,required=True);ap.add_argument('--struct-header',type=Path,required=True) ap.add_argument('--program-workers',action='store_true') + ap.add_argument('--flash-tick-period',type=int,default=37) + ap.add_argument('--flash-part',choices=['w25q32','gt25q32'],default='w25q32') ap.add_argument('--cc',default='cc');args=ap.parse_args();rng=random.Random(0x5b11) + if args.flash_part!='w25q32' and not args.program_workers:ap.error('--flash-part requires --program-workers') + if not 1<=args.flash_tick_period<=100000:ap.error('invalid flash tick period') ll=read_header(args.ll_header,'d1331e0d99ff2377e80e7ec4b8f314453345e68d894f5d2b3139c0e8069ae724') struct=read_header(args.struct_header,'97fffd958e992ea25af94e08bb6e5b6c7487671b3e070efa68e80d216db383b2') helpers=[] @@ -106,7 +110,7 @@ def main(): end+=1 helpers.append(ll[match.start():end]) notice=ll[:ll.index('#pragma once')] - total=0;calls=0;loaded=0;busy_turns=0 + total=0;calls=0;loaded=0;busy_turns=0;busy_polls=0 with tempfile.TemporaryDirectory(prefix='embsim-spi-c-') as tmp: out=Path(tmp);(out/'vendor-spi-struct.h').write_text(struct,encoding='utf-8') def compile_code(name,source,base,entry,flags=()): @@ -120,6 +124,8 @@ def compile_code(name,source,base,entry,flags=()): if len(f:=line.split())==3} return binary.read_bytes(),symbols[entry] body=SOURCE.replace(' dev->ctrl.fastrd_mode=0;', PROGRAM_PREFIX+' dev->ctrl.fastrd_mode=0;',1) if args.program_workers else SOURCE + if args.program_workers and args.flash_tick_period!=37: + body=body.replace('} while(dev->data_buf[0]&1);', 'if(dev->data_buf[0]&1) (*(volatile unsigned *)0x3ffc1004)++;\n } while(dev->data_buf[0]&1);') source=notice+'\n#include \n#include \n#include "vendor-spi-struct.h"\n'+'\n'.join(helpers)+body code,start=compile_code('operation',source,0x40080000,'operation') image=bytearray(rng.getrandbits(8) for _ in range(0x400000 if args.program_workers else 4*65536));entries=[];lengths=[];payloads=[];tags=[0x12345678,0x98765432] @@ -131,7 +137,7 @@ def compile_code(name,source,base,entry,flags=()): entries.append(entry);lengths.append(len(worker)) image=bytes(image);lib=out/'spi.so' sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','machine.c','matrix.c','flashmap.c','flashspi.c', 'w25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c', 'efuse.c','machine.c','matrix.c','flashmap.c','flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT/'core/util/alloc.c'] subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC', '-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) @@ -144,7 +150,7 @@ def bind(name,result,*params): fabric=bind('esk_esp32_machine_fabric',pointer,pointer) bus_get=bind('esk_esp32_machine_bus',Memory,pointer,C.c_uint) load=bind('esk_esp32_fabric_load',C.c_int,pointer,C.c_uint,C.c_uint32,pointer,C.c_size_t) - select=bind('esk_esp32_fabric_w25q32_configure',C.c_int,pointer,C.c_int) + select=bind('esk_esp32_fabric_'+args.flash_part+'_configure',C.c_int,pointer,C.c_int) advance=bind('esk_esp32_fabric_flash_advance',C.c_int,pointer,C.c_uint64) deadline=bind('esk_esp32_fabric_flash_deadline',C.c_int,pointer,C.POINTER(C.c_uint64)) flash=bind('esk_esp32_fabric_flash',C.c_int,pointer,pointer,C.c_size_t) @@ -182,12 +188,12 @@ def bind(name,result,*params): states.append(w);assert run(m,cpu,1)==0 done=[False,False];event=C.c_uint();fault=Fault();trace=[];worker_steps=[0,0] for tick in range(30000): - # Explicit external flash-time fixture: 1 ms events every 37 + # Explicit external flash-time fixture: 1 ms events at the selected # scheduled turns. This is not a CPU instruction/cycle timing model. if args.program_workers: remaining=C.c_uint64();assert deadline(ram,C.byref(remaining))==0 busy_turns+=remaining.value!=0 - if tick%37==0: assert advance(ram,1000)==0 + if tick%args.flash_tick_period==0: assert advance(ram,1000)==0 cpu=(tick+trial)%2;w=states[cpu] if done[cpu]:continue if w.cpu.pc==0x7ffffff0: @@ -210,6 +216,10 @@ def bind(name,result,*params): assert actual==expected[cpu][1] assert bool(worker_steps[cpu])==bool(params[cpu][3]);loaded+=bool(worker_steps[cpu]);calls+=1 assert bus.read(bus.ctx,0x3ffc1000,4,C.byref(read))==0 and read.value==0 + if args.program_workers and args.flash_tick_period!=37: + assert bus.read(bus.ctx,0x3ffc1004,4,C.byref(read))==0 + if params[0][3] and args.flash_tick_period>=257:assert read.value>0 + busy_polls+=read.value assert bus.read(bus.ctx,0x3ff000ec,4,C.byref(read))==0 and not read.value&(1<<29) result=trace,[w.cpu.a[6] for w in states] if replay:assert result==previous @@ -217,8 +227,8 @@ def bind(name,result,*params): finally:destroy(m) assert a.live_bytes==0 if args.program_workers: assert busy_turns>0 - action='SPI-programmed' if args.program_workers else 'SPI-loaded' - print(f'{calls} actual ESP32 SPI1 C reads, {loaded} {action} RAM workers, 32 exact replays, {total} retired instructions; {busy_turns} busy turns; seven unchanged SDK helper bodies') + action=(args.flash_part+'-programmed') if args.program_workers else 'SPI-loaded' + print(f'{calls} actual ESP32 SPI1 C reads, {loaded} {action} RAM workers, 32 exact replays, {total} retired instructions; {busy_turns} busy turns, {busy_polls} instrumented firmware BUSY reads; seven unchanged SDK helper bodies') if __name__=='__main__':main() diff --git a/tests/vendor/xtensa_integer/run_tls_c.py b/tests/vendor/xtensa_integer/run_tls_c.py index 6a4d1ecf6..e937dbde9 100644 --- a/tests/vendor/xtensa_integer/run_tls_c.py +++ b/tests/vendor/xtensa_integer/run_tls_c.py @@ -83,7 +83,7 @@ def main(): assert int.from_bytes(template[symbols['zero']:symbols['zero'] + 4], 'little') == 0 lib = out / 'tls.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_w25q32.py b/tests/vendor/xtensa_integer/run_w25q32.py index a81a8d300..22f6accfa 100644 --- a/tests/vendor/xtensa_integer/run_w25q32.py +++ b/tests/vendor/xtensa_integer/run_w25q32.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 # Copyright 2026 Junior Deogracias -"""W25Q32JV protocol oracle and hash-pinned Rev.G maximum-duration facts. +"""Named 4MiB NOR oracle: Winbond Rev.G or --part gt25q32 Giantec V2.5. PDF remains external, requires pdftotext. Does not identify the requested board's chip or qualify power loss, protection, electrical timing or full command set. """ import argparse import ctypes as C +from decimal import Decimal import hashlib from pathlib import Path import random @@ -23,27 +24,38 @@ class NOR(C.Structure): def main(): ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('--datasheet',type=Path,required=True) + ap.add_argument('--part',choices=['w25q32','gt25q32'],default='w25q32') ap.add_argument('--cc',default='cc');args=ap.parse_args();rng=random.Random(0x2532) - assert hashlib.sha256(args.datasheet.read_bytes()).hexdigest()=='b2585095ac503e7cee7916ab3f025a51699b49e2cad7f6782e92b828c1cc09d8' + giant=args.part=='gt25q32' + digest='df021758e55bde131f39af59c42acac20f2e0b32e251f4ade673687d22b7cd55' if giant else 'b2585095ac503e7cee7916ab3f025a51699b49e2cad7f6782e92b828c1cc09d8' + assert hashlib.sha256(args.datasheet.read_bytes()).hexdigest()==digest text=subprocess.check_output(['pdftotext','-layout',str(args.datasheet),'-'],text=True) - names={2:'tPP',0x20:'tSE',0x52:'tBE1',0xd8:'tBE2',0xc7:'tCE',0x60:'tCE'};times={} + names={2:'tPP',0x20:'tSE',0x52:'tBE1',0xd8:'tBE2',0xc7:'tCE',0x60:'tCE'};times={};byte_time=0 for cmd,symbol in names.items(): - match=re.search(r'\b'+symbol+r'\s+([\d.]+)\s+([\d,]+)\s+(ms|s)\b',text);assert match,symbol - times[cmd]=int(match[2].replace(',',''))*(1000 if match[3]=='ms' else 1000000) - identity=int(re.search(r'W25Q32JV-IQ/JQ\s+15h\s+([0-9A-F]+)h',text)[1],16) - manufacturer=int(re.search(r'Winbond Serial Flash\s+([0-9A-F]+)h',text)[1],16) - jedec=bytes([manufacturer,identity>>8,identity&255]) - for name,cmd in [('Page Program',2),(r'Sector Erase \(4KB\)',0x20),(r'Block Erase \(32KB\)',0x52),(r'Block Erase \(64KB\)',0xd8)]: - assert int(re.search(name+r'\s+([0-9A-F]+)h\s+A23-A16',text)[1],16)==cmd + pattern=r'\b'+symbol+(r'[^\n]*?\s+([\d.]+)\s+([\d,.]+)\s+(ms|s)\b' if giant else r'\s+([\d.]+)\s+([\d,]+)\s+(ms|s)\b') + match=re.search(pattern,text);assert match,symbol + times[cmd]=int(Decimal(match[2].replace(',',''))*(1000 if match[3]=='ms' else 1000000)) + if giant: + match=re.search(r'9FH\s+([0-9A-F]+)h\s+([0-9A-F]+)\s+([0-9A-F]+)',text);assert match + jedec=bytes(int(value,16) for value in match.groups()) + match=re.search(r'\btBP[^\n]*?\s+([\d.]+)\s+([\d.]+)\s+µs',text);assert match + byte_time=int(Decimal(match[2])) + assert '256 data bytes' in text and 'unspecified time' in text + else: + identity=int(re.search(r'W25Q32JV-IQ/JQ\s+15h\s+([0-9A-F]+)h',text)[1],16) + manufacturer=int(re.search(r'Winbond Serial Flash\s+([0-9A-F]+)h',text)[1],16) + jedec=bytes([manufacturer,identity>>8,identity&255]) + for name,cmd in [('Page Program',2),(r'Sector Erase \(4KB\)',0x20),(r'Block Erase \(32KB\)',0x52),(r'Block Erase \(64KB\)',0xd8)]: + assert int(re.search(name+r'\s+([0-9A-F]+)h\s+A23-A16',text)[1],16)==cmd with tempfile.TemporaryDirectory(prefix='embsim-w25q32-ref-') as tmp: lib=Path(tmp)/'nor.so' subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC', - '-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),str(ROOT/'core/mcu/esp32/w25q32.c'),'-o',str(lib)],check=True) + '-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),str(ROOT/'core/mcu/esp32'/(args.part+'.c')),str(ROOT/'core/mcu/esp32/nor32.c'),'-o',str(lib)],check=True) api=C.CDLL(str(lib));p=C.POINTER(NOR) - init=api.esk_esp32_w25q32_init;init.argtypes=[p];init.restype=None - command=api.esk_esp32_w25q32_command + init=getattr(api,'esk_esp32_'+args.part+'_init');init.argtypes=[p];init.restype=None + command=getattr(api,'esk_esp32_'+args.part+'_command') command.argtypes=[p,C.c_void_p,C.c_size_t,C.c_uint,C.c_int,C.c_uint32,C.c_void_p,C.c_size_t,C.c_void_p,C.c_size_t];command.restype=C.c_int - advance=api.esk_esp32_w25q32_advance;advance.argtypes=[p,C.c_void_p,C.c_size_t,C.c_uint64];advance.restype=C.c_int + advance=getattr(api,'esk_esp32_'+args.part+'_advance');advance.argtypes=[p,C.c_void_p,C.c_size_t,C.c_uint64];advance.restype=C.c_int size=4*1024*1024;expected=bytearray(b'\xff'*size);actual=C.create_string_buffer(bytes(expected),size) nor=NOR();init(C.byref(nor));wel=False;pending=None;checks=0;completed=0;ignored=0 def call(cmd,address=None,data=b'',read=0): @@ -70,17 +82,21 @@ def tick(amount): for trial in range(256): if trial%23==0:cmd=[0x20,0x52,0xd8,0xc7,0x60][(trial//23)%5] else:cmd=2 - address=rng.randrange(size);data=rng.randbytes(rng.randint(1,256)) if cmd==2 else b'' + address=rng.randrange(size);length=([1,2,16,64,255,256][trial-1] if 1<=trial<=6 else rng.randint(1,256)) + data=rng.randbytes(length) if cmd==2 else b'' if trial%7: call(6);wel=True + authorized=wel;duration=byte_time if giant and cmd==2 and len(data)==1 else times[cmd] call(cmd,address if cmd not in [0xc7,0x60] else None,data) - if wel:pending=cmd,address if cmd not in [0xc7,0x60] else 0,data,times[cmd] + if authorized: + pending=cmd,address if cmd not in [0xc7,0x60] else 0,data,duration + if giant:wel=False else:ignored+=1 - assert nor.remaining_us==(times[cmd] if wel else 0) - assert call(5,read=7)==bytes([3 if wel else 0])*7 + assert nor.remaining_us==(duration if authorized else 0) + assert call(5,read=7)==bytes([(1 if pending else 0)|(2 if wel else 0)])*7 if pending: snapshot=bytes(nor);call(4);call(6);assert bytes(nor)==snapshot - tick(0);tick(times[cmd]-1) + tick(0);tick(duration-1) # All cells still match the previous completed state. assert actual.raw==expected tick(1) @@ -90,7 +106,7 @@ def tick(amount): assert call(0x9f,read=3)==jedec assert call(3,address,data=b'',read=min(64,size-address))==expected[address:address+min(64,size-address)] assert not nor.remaining_us and not nor.wel - print(f'{checks} independent NOR command/tick comparisons, {completed} completed operations, {ignored} writes ignored without WEL; maximum durations and IQ ID parsed from pinned datasheet') + print(f'{args.part}: {checks} independent NOR command/tick comparisons, {completed} completed operations, {ignored} writes ignored without WEL; maximum durations and ID parsed from pinned datasheet') if __name__=='__main__':main() From 6638748dd118f5c245cf99ab3c7e55d78ea988c9 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 15:49:16 +0000 Subject: [PATCH 28/57] Preserve active ESP32 eFuse reads until external completion --- CHANGELOG.md | 4 +- core/mcu/esp32/efuse.c | 15 ++++-- core/mcu/esp32/efuse.h | 8 ++- core/mcu/esp32/fabric.c | 3 ++ core/mcu/esp32/fabric.h | 2 + docs/STATUS.md | 4 +- docs/engineering/esp32-boot-verification.md | 30 +++++++---- docs/engineering/esp32-dual-core.md | 4 +- docs/engineering/esp32-efuse-read-profile.md | 44 ++++++++++++---- tests/unit/test_esp32_efuse.c | 20 +++++++- tests/vendor/README.md | 6 ++- .../evidence/boot-with-efuse-2026-11-05.json | 3 +- ...boot-with-efuse-completion-2026-11-05.json | 51 +++++++++++++++++++ tests/vendor/xtensa_integer/probe_rom_boot.py | 17 +++++-- tests/vendor/xtensa_integer/run_efuse.py | 9 +++- 15 files changed, 181 insertions(+), 39 deletions(-) create mode 100644 tests/vendor/xtensa_integer/evidence/boot-with-efuse-completion-2026-11-05.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 0980c725e..2bddb9fc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,9 @@ versions follow semantic versioning. There is no release yet. read protection, completion status and source-44 routing. OTP programming, other coding schemes and hardware fuse consumers remain unqualified. - A supplied synthetic unprogrammed image advances the real ROM probe from seven - to 30 retired instructions; boot now blocks on RTC control at 0x3ff48000. + to 440 retired instructions with explicit read-completion events; boot now blocks + on WSR.ATOMCTL at PC 0x40000488. Immediate completion previously selected the + ROM failure/reset path; reads now stay active until an external completion event. This does not establish native SDK boot or reproduce the owner's fuse contents. - Added a separate Giantec GT25Q32A-U subset matching reported C4 60 16, with independently qualified maximum-duration and WEL policies. Shared array code diff --git a/core/mcu/esp32/efuse.c b/core/mcu/esp32/efuse.c index c4808e594..109fb1558 100644 --- a/core/mcu/esp32/efuse.c +++ b/core/mcu/esp32/efuse.c @@ -46,7 +46,8 @@ es_status esk_esp32_efuse_read(const esk_esp32_efuse *f,uint32_t address,unsigne if(kind<2) { *value=kind ? f->write[index] : f->read[index];return ES_OK; } switch(index) { case 0xf8:*value=f->clock;break;case 0xfc:*value=f->conf;break; - case 0x100:case 0x104:case 0x11c:*value=0;break; + case 0x100:if(f->pending) return ES_EUNSUPPORTED;*value=0;break; + case 0x104:*value=f->pending;break;case 0x11c:*value=0;break; case 0x108:*value=f->raw;break;case 0x10c:*value=f->raw&f->enable;break; case 0x110:*value=f->enable;break;case 0x118:*value=f->dac;break;case 0x1fc:*value=f->date;break; default:return ES_EUNSUPPORTED; @@ -59,7 +60,8 @@ es_status esk_esp32_efuse_write(esk_esp32_efuse *f,uint32_t address,unsigned byt if(!f) return ES_EINVAL; st=decode(f,address,bytes,&kind,&index);if(st!=ES_OK) return st; if(kind==0) return ES_OK; /* TRM software-read registers are RO. */ - if(kind==1) { f->write[index]=value;return ES_OK; } + if(kind==1) { if(f->pending) return ES_EUNSUPPORTED;f->write[index]=value;return ES_OK; } + if(f->pending && (index==0xf8 || index==0xfc || index==0x118 || (index==0x104 && (value&3u)))) return ES_EUNSUPPORTED; switch(index) { case 0xf8:f->clock=value&0x1ffffu;break; case 0xfc: @@ -71,7 +73,7 @@ es_status esk_esp32_efuse_write(esk_esp32_efuse *f,uint32_t address,unsigned byt case 0x104: value&=3u;if(!value) break; if(value!=1 || f->conf!=0x5aa5u) return ES_EUNSUPPORTED; - reload(f);f->raw|=1;break; + f->pending=1;break; case 0x110:f->enable=value&3u;break; case 0x114:f->raw&=~(value&3u);break; case 0x118:if(value&256u) return ES_EUNSUPPORTED;f->dac=value&255u;break; @@ -87,3 +89,10 @@ es_status esk_esp32_efuse_irq(const esk_esp32_efuse *f,int *asserted) if(!f->enabled) return ES_EUNSUPPORTED; *asserted=(f->raw&f->enable)!=0;return ES_OK; } + +es_status esk_esp32_efuse_read_complete(esk_esp32_efuse *f) +{ + if(!f) return ES_EINVAL; + if(!f->enabled || !f->pending) return ES_EUNSUPPORTED; + reload(f);f->pending=0;f->raw|=1;return ES_OK; +} diff --git a/core/mcu/esp32/efuse.h b/core/mcu/esp32/efuse.h index b697d1141..cb7288d27 100644 --- a/core/mcu/esp32/efuse.h +++ b/core/mcu/esp32/efuse.h @@ -10,18 +10,22 @@ * RDATA is read-only; WDATA stages bits but PGM and write opcodes explicitly * fail: no burn/OTP, write-protection, ECC, analog timing, supply failure, * global reset or hidden-hardware consumer qualification. READ_CMD completes - * synchronously in qualified read mode, sets raw completion, and IRQ=raw&enable. + * only on an explicit external read-completion event; CMD stays active until then. + * Completion reloads latches, sets raw completion, and IRQ=raw&enable. * CLK/DAC fields are configuration storage, not physical waveform timing. * init/attach is host fixture setup; CPU-only reset does not reset this device. * All calls serialized, no re-entry. Missing/unselected profile fails explicitly. */ typedef struct esk_esp32_efuse { uint32_t image[31],read[31],write[31]; - uint32_t enabled,clock,conf,raw,enable,dac,date; + uint32_t enabled,clock,conf,raw,enable,dac,date,pending; } esk_esp32_efuse; void esk_esp32_efuse_init(esk_esp32_efuse *f); es_status esk_esp32_efuse_configure(esk_esp32_efuse *f,const uint32_t *words,size_t count); es_status esk_esp32_efuse_read(const esk_esp32_efuse *f,uint32_t address,unsigned bytes,uint32_t *value); es_status esk_esp32_efuse_write(esk_esp32_efuse *f,uint32_t address,unsigned bytes,uint32_t value); +/* External controller-completion event, not a CPU step or assumed duration. + * Disabled/idle -> ES_EUNSUPPORTED; state unchanged. Call outside CPU execution. */ +es_status esk_esp32_efuse_read_complete(esk_esp32_efuse *f); /* ES_EUNSUPPORTED when profile disabled; output unchanged on failure. */ es_status esk_esp32_efuse_irq(const esk_esp32_efuse *f,int *asserted); #endif diff --git a/core/mcu/esp32/fabric.c b/core/mcu/esp32/fabric.c index 9288e98ee..9a92ac7d1 100644 --- a/core/mcu/esp32/fabric.c +++ b/core/mcu/esp32/fabric.c @@ -181,3 +181,6 @@ esk_xtensa_memory esk_esp32_fabric_bus(esk_esp32_fabric *f,unsigned cpu) if(f && cpu<2) { bus.ctx=&f->view[cpu];bus.read=read_bus;bus.write=write_bus;bus.compare_exchange=cas_bus;bus.fetch=fetch_bus; } return bus; } + +es_status esk_esp32_fabric_efuse_read_complete(esk_esp32_fabric *f) +{ return f ? esk_esp32_efuse_read_complete(&f->efuse) : ES_EINVAL; } diff --git a/core/mcu/esp32/fabric.h b/core/mcu/esp32/fabric.h index 3a1da7fe0..3d74f6172 100644 --- a/core/mcu/esp32/fabric.h +++ b/core/mcu/esp32/fabric.h @@ -51,6 +51,8 @@ es_status esk_esp32_fabric_flash_deadline(const esk_esp32_fabric *f,uint64_t *mi * BLK0(7), BLK1(8), BLK2(8), BLK3(8) order. No factory defaults inferred. * NULL/0 detaches. Host configuration is only allowed between machine turns. */ es_status esk_esp32_fabric_efuse_configure(esk_esp32_fabric *f,const uint32_t *words,size_t count); +/* Explicit external completion; next machine step synchronizes source 44. */ +es_status esk_esp32_fabric_efuse_read_complete(esk_esp32_fabric *f); es_status esk_esp32_fabric_efuse_irq(const esk_esp32_fabric *f,int *asserted); /* Host-controlled boot-remap bit, not a DPORT register model. */ es_status esk_esp32_fabric_remap(esk_esp32_fabric *f,unsigned cpu,int enabled); diff --git a/docs/STATUS.md b/docs/STATUS.md index 6bb0fef55..9ac9bffa0 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -10,7 +10,9 @@ does not mean the described behaviour exists. **Current implementation update:** an [explicit eFuse read/reload profile](engineering/esp32-efuse-read-profile.md) now supports None-coded input images, read protection and completion routing. With a supplied synthetic unprogrammed image, real ROM execution advances from -seven to 30 retired instructions, then blocks on RTC control at 0x3ff48000. +seven to 440 retired instructions, then blocks on WSR.ATOMCTL at PC 0x40000488. +Read completion is an explicit external event; no physical duration is inferred. +The earlier synchronous profile took the ROM failure/reset path. The owner's actual fuse image is not inferred. A separate [Giantec GT25Q32A-U storage subset](engineering/esp32-giantec-profile.md) now matches the reported ID, with independent timing/WEL behavior. Full native boot, diff --git a/docs/engineering/esp32-boot-verification.md b/docs/engineering/esp32-boot-verification.md index 1bee94c12..32b52a03c 100644 --- a/docs/engineering/esp32-boot-verification.md +++ b/docs/engineering/esp32-boot-verification.md @@ -3,8 +3,10 @@ **Result on 2026-11-05: the owner's board reports ESP32-D0WD-V3 revision 3.1, JEDEC C4 60 16 and 4 MB flash. Giantec's GT25Q32A-U datasheet matches that ID; exact package/ordering suffix remains unconfirmed. The pinned SDK example boots -in Espressif QEMU; native EmbSIM fails at the eFuse read after seven retired -instructions.** Passing fixtures and CI do not establish successful native boot. +in Espressif QEMU. Native EmbSIM stops at unsupported WSR.ATOMCTL after 440 retired +instructions with the new explicit synthetic eFuse image; without that profile, +the baseline stops at an eFuse read after seven.** Neither result establishes +successful native boot. ## Physical readout and manufacturer match @@ -37,8 +39,10 @@ markings or an authenticated BOM would resolve the remaining ordering details. The observed ID differs from the W25Q32JV-IQ fixture's EF 40 16. That Winbond fixture must not be selected as verified hardware for this board or relabeled -with C4 without qualifying Giantec-specific behavior. No runtime chip profile -was changed by this evidence update; a Giantec model still needs qualification. +with C4 without qualifying Giantec-specific behavior. The subsequent +[named Giantec profile](esp32-giantec-profile.md) now qualifies a bounded standard +SPI array protocol and separately selected timing bounds. It does not establish +the exact fitted die or complete chip/board behavior. ## Listing and nominal module evidence @@ -112,8 +116,8 @@ configuration or printed SDK capacity must not substitute for hardware identity. **Implementation follow-up:** the new [eFuse read profile](esp32-efuse-read-profile.md) accepts an explicitly supplied image. The synthetic unprogrammed fixture advances -the same ROM to 30 retired instructions and an unimplemented RTC write at -0x3ff48000/PC 0x4000fdcd. That is not the owner's factory image and still does not +the same ROM to 440 retired instructions and an unsupported WSR.ATOMCTL at +PC 0x40000488, with explicitly supplied controller-completion events. That is not the owner's factory image and still does not boot the application. The baseline no-eFuse-profile result below remains valid. @@ -131,14 +135,18 @@ fault load failure (kind 5), status -1 ``` Pinned SDK `soc/esp32/include/soc/efuse_reg.h` identifies that address as -`EFUSE_BLK0_RDATA0_REG`. The current bus has no eFuse controller/profile. This -is the **first observed blocker**, not a claim that adding one register would -finish boot. RTC/strapping/reset state, further eFuse behavior, dedicated flash +`EFUSE_BLK0_RDATA0_REG`. This baseline deliberately has no eFuse profile +attached. With the new explicit image attached, the first observed blocker +moves to WSR.ATOMCTL, as recorded above. The earlier synchronous profile took +the ROM failure path and requested a software reset through RTC options; +it did not pass the eFuse handshake. RTC/strapping/reset state, +further eFuse behavior, dedicated flash commands/cache modes, other MMIO and complete CPU/SDK execution remain separate requirements. Dummy zero reads would hide this gap and cannot verify boot. -The probe uses the supported CPU reset profile and an explicit PRID=0 fixture, -not a silicon-qualified full PRID. It installs only the PRO ROM; APP-specific +The probe uses the supported CPU reset profile. Its default PRO PRID=0xcdcd +comes from pinned ESP-IDF `xtensa_context.h`, not a readout of the owner chip. +Use `--prid 0` to reproduce the historical explicit invalid-ID fixture. It installs only the PRO ROM; APP-specific ROM behavior is not tested. Missing DROM tail bytes are zero-filled to match the QEMU region setup, not asserted as physical ROM contents. The blob is an Espressif QEMU distribution input, not an authenticated dump of the owner's chip. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 03c25d16c..26b055e65 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -28,8 +28,8 @@ and differs from the separate Winbond test profile. The [explicit eFuse read/reload profile](esp32-efuse-read-profile.md) now handles that first access when a caller supplies all 31 uncoded words. A synthetic -unprogrammed fixture advances ROM to 30 retired instructions and the next -unimplemented RTC write at 0x3ff48000. It is not the owner's fuse dump and does +unprogrammed fixture with explicit read-completion events advances ROM to 440 +retired instructions and unsupported WSR.ATOMCTL at PC 0x40000488. It is not the owner's fuse dump and does not establish full boot. The [Giantec storage subset](esp32-giantec-profile.md) adds independently checked timing/WEL behavior for the matching JEDEC family; complete chip behavior remains unqualified. diff --git a/docs/engineering/esp32-efuse-read-profile.md b/docs/engineering/esp32-efuse-read-profile.md index 0d15ae067..2ba8b1f68 100644 --- a/docs/engineering/esp32-efuse-read-profile.md +++ b/docs/engineering/esp32-efuse-read-profile.md @@ -24,8 +24,15 @@ These inputs are not assertions about a particular unit's unique fuse contents. WDATA stores staging words but cannot program the image. The supported handshake is CONF=0x5aa5 followed by CMD=1: the controller reloads the protected view, -self-clears CMD and latches read-complete RAW bit 0. Completion is synchronous; -physical read/program timing and bus clocks are unqualified. RAW is independent +sets CMD=1 until `esk_esp32_efuse_read_complete` supplies an external controller +completion event. That event reloads the protected latches, clears CMD and latches +read-complete RAW bit 0. Polling does not advance the operation. Completion while +idle/disabled fails unchanged; reissuing a command or changing read configuration, +clock/DAC or staging while pending is explicitly unsupported. STATUS state-machine +encoding while active remains unsupported. A zero command write does not cancel. +Physical read/program duration and bus clocks are unqualified; callers must supply +the external completion event rather than assuming instruction retirement is time. +RAW is independent of ENA; ST and the level IRQ are RAW AND ENA. CLR is write-one-to-clear; reading that write-only register is unsupported. Source 44 is owned by the machine while the profile is attached, with synchronization after instruction commit or @@ -48,7 +55,7 @@ remain unqualified. The image/read profile does not constitute a full SoC model. ## Evidence -`test_esp32_efuse` passes 8,163 assertions across all software-read protection +`test_esp32_efuse` passes 8,443 assertions across all software-read protection combinations and both None encodings, all read/staging words, unchanged rejected images/commands, immutable host input, completion/masking/clear, CPU-only reset, source ownership and a fetched READ_CMD store followed by level-2 IRQ entry. @@ -56,7 +63,9 @@ source ownership and a fetched READ_CMD store followed by level-2 IRQ entry. `run_efuse.py` hash-pins ESP-IDF v4.4.7 register, low-level and SoC headers. It passes 3,968 independent register/protection comparisons. Actual ESP32 compiled C then executes seven unchanged selected SDK helper bodies through native MMIO: -128 calls, 5,248 retired instructions, on both serialized cores. Register +128 calls, 6,208 retired instructions, on both serialized cores. The fixture supplies completion opportunities every +17 instruction attempts, an artificial external event schedule. Firmware calls +are serialized across the two cores because the controller is shared. Register addresses and bit fields come from the vendor headers. A minimal MMIO macro fixture supplies bus access, not a vendor controller implementation. Temporary header/helper extraction retains Espressif's Apache-2.0 notice. Synthetic fuse @@ -71,19 +80,31 @@ With the explicitly selected the same QEMU-distributed PRO ROM and actual ESP-IDF flash image now reach: ```text -retired instructions 30 -failure PC 0x4000fdcd -failure address 0x3ff48000 -fault store failure, RTC_CNTL_OPTIONS0_REG +retired instructions 440 +failure PC 0x40000488 +failure opcode 0x136330 +fault unsupported WSR.ATOMCTL (special register 99) ``` -The [recorded probe](../../tests/vendor/xtensa_integer/evidence/boot-with-efuse-2026-11-05.json) +The [recorded probe](../../tests/vendor/xtensa_integer/evidence/boot-with-efuse-completion-2026-11-05.json) is a **blocked boot**, not application success. The test fuse image is deliberately unprogrammed; it is neither a dump of the owner's v3.1 unit nor a claim about QEMU's full factory image. The ROM itself consumes the controller data. There are no unimplemented-MMIO fallback values or host PC shortcuts. PRID/reset/DROM tail fixture limitations from the [boot investigation](esp32-boot-verification.md) -still apply. RTC/power/reset and subsequent boot requirements remain unfinished. +still apply. The fixture supplies completion opportunities every 64 instruction +attempts and PRO PRID=0xcdcd from the pinned SDK documentation. Three reads +complete before the unsupported instruction. The schedule is not physical timing. Repeating with `--efuse-completion-period +128` reaches the same instruction after 503 retirements; period 1 completes +before the ROM sample and correctly reproduces its reset request after 30. + +The previous synchronous implementation's 30-instruction RTC write was a **ROM +software-reset request**, not evidence of passing its eFuse handshake. Disassembly +shows the ROM expects to observe READ_CMD active before waiting for completion; +instant completion selected the failure/reset path. The new model preserves that +observable active state without inventing a read duration. Historical evidence +retains the old result; the event-driven probe supersedes it. RTC/power/reset, +ATOMCTL/cache/atomic permissions and subsequent boot requirements remain unfinished. ## References and reproduction @@ -93,6 +114,9 @@ ESP-IDF v4.4.7 external inputs: `cfe37e8e692a22e41885f606bee7c4c12e0308abbdeb5f13f6cc92471453ff03`. - `components/hal/esp32/include/hal/efuse_ll.h`, SHA-256 `ce3dfed567383582c6ced10a9edc08de296c5b5d541ee171efdd07b0014e3f30`. +- `components/xtensa/include/xtensa/xtensa_context.h` documents PRO PRID=0xcdcd + and APP=0xabab; SHA-256 + `a833937d99b2c5147e2836203643590ad2c717b2b406be31388a5a9a3c5c4aca`. - `soc.h` and actual ESP32 toolchain pins are in the target investigation. ```sh diff --git a/tests/unit/test_esp32_efuse.c b/tests/unit/test_esp32_efuse.c index 4ae4f7c87..923a24f04 100644 --- a/tests/unit/test_esp32_efuse.c +++ b/tests/unit/test_esp32_efuse.c @@ -39,6 +39,20 @@ static void reload_interrupt_and_failures(void) before=f;CHECK_EQ(esk_esp32_efuse_write(&f,0x3ff5a104,4,1),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&f,&before,sizeof f),0); CHECK_EQ(esk_esp32_efuse_write(&f,0x3ff5a0fc,4,0x5aa5),ES_OK); CHECK_EQ(esk_esp32_efuse_write(&f,0x3ff5a104,4,1),ES_OK); + CHECK_EQ(esk_esp32_efuse_read(&f,0x3ff5a104,4,&value),ES_OK);CHECK_EQ(value,1); + CHECK_EQ(esk_esp32_efuse_read(&f,0x3ff5a108,4,&value),ES_OK);CHECK_EQ(value,0); + before=f;CHECK_EQ(esk_esp32_efuse_write(&f,0x3ff5a104,4,1),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&f,&before,sizeof f),0); + CHECK_EQ(esk_esp32_efuse_write(&f,0x3ff5a0fc,4,0),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&f,&before,sizeof f),0); + for(i=0;i<64;i++) { + CHECK_EQ(esk_esp32_efuse_read(&f,0x3ff5a104,4,&value),ES_OK);CHECK_EQ(value,1); + CHECK_EQ(esk_esp32_efuse_irq(&f,&irq),ES_OK);CHECK_EQ(irq,0); + } + CHECK_EQ(esk_esp32_efuse_write(&f,0x3ff5a104,4,0),ES_OK);CHECK_EQ(f.pending,1); + before=f;CHECK_EQ(esk_esp32_efuse_write(&f,stage_reg(0),4,0),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&f,&before,sizeof f),0); + CHECK_EQ(esk_esp32_efuse_read(&f,0x3ff5a100,4,&value),ES_EUNSUPPORTED); + CHECK_EQ(esk_esp32_efuse_read_complete(&f),ES_OK); + before=f;CHECK_EQ(esk_esp32_efuse_read_complete(&f),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&f,&before,sizeof f),0); + CHECK_EQ(esk_esp32_efuse_read(&f,0x3ff5a104,4,&value),ES_OK);CHECK_EQ(value,0); CHECK_EQ(esk_esp32_efuse_read(&f,0x3ff5a108,4,&value),ES_OK);CHECK_EQ(value,1); CHECK_EQ(esk_esp32_efuse_read(&f,0x3ff5a10c,4,&value),ES_OK);CHECK_EQ(value,0); CHECK_EQ(esk_esp32_efuse_write(&f,0x3ff5a110,4,1),ES_OK);CHECK_EQ(esk_esp32_efuse_irq(&f,&irq),ES_OK);CHECK_EQ(irq,1); @@ -69,7 +83,11 @@ static void native_bus_and_reset_retention(void) CHECK_EQ(esk_esp32_machine_configure(m,0,0x40080000),ES_OK);w=esk_esp32_machine_core(m,0); w->irq.enabled=1;w->irq.intenable=1u<<19;w->irq.vectors[2]=0x40084000;w->cpu.a[2]=0x3ff5a104;w->cpu.a[3]=1; CHECK_EQ(esk_esp32_machine_run_core(m,0,1),ES_OK);CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK); - CHECK_EQ(event,0);CHECK_EQ(w->cpu.pc,0x40080003);CHECK(w->irq.lines&(1u<<19)); + CHECK_EQ(event,0);CHECK_EQ(w->cpu.pc,0x40080003);CHECK(!(w->irq.lines&(1u<<19))); + CHECK_EQ(esk_esp32_machine_app_reset_configure(m,1),ES_OK); + CHECK_EQ(bus.read(bus.ctx,0x3ff5a104,4,&value),ES_OK);CHECK_EQ(value,1); + CHECK_EQ(esk_esp32_fabric_efuse_irq(f,&irq),ES_OK);CHECK_EQ(irq,0); + CHECK_EQ(esk_esp32_fabric_efuse_read_complete(f),ES_OK); CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK);CHECK_EQ(event,12);CHECK_EQ(w->irq.epc[2],0x40080003); CHECK_EQ(esk_esp32_machine_app_reset_configure(m,1),ES_OK);CHECK_EQ(esk_esp32_fabric_efuse_irq(f,&irq),ES_OK);CHECK_EQ(irq,1); CHECK_EQ(bus.read(bus.ctx,data_reg(1),4,&value),ES_OK);CHECK_EQ(value,0x12345678); diff --git a/tests/vendor/README.md b/tests/vendor/README.md index 39644b756..bd1f7d840 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -193,10 +193,12 @@ See [boot verification and artifacts](../../docs/engineering/esp32-boot-verifica `xtensa_integer/run_efuse.py` takes pinned ESP-IDF v4.4.7 `--efuse-reg`, `--efuse-ll`, `--soc-header` and actual ESP32 compiler/objcopy/linker/nm tools: -3,968 register/protection comparisons and 128 compiled C calls/5,248 instructions +3,968 register/protection comparisons and 128 compiled C calls/6,208 instructions through seven unchanged SDK helpers. Explicit synthetic None-coded images only; no owner keys or full burn/SDK qualification. Add `--efuse FILE` to the ROM probe -for the [new eFuse profile](../../docs/engineering/esp32-efuse-read-profile.md). +for the [new eFuse profile](../../docs/engineering/esp32-efuse-read-profile.md): +with explicit external completion opportunities every 64 instruction attempts, +it reaches 440 retired instructions and unsupported WSR.ATOMCTL, not SDK boot. `run_w25q32.py --part gt25q32 --datasheet FILE` uses the hash-pinned Giantec V2.5 PDF and an independent array/time oracle. `run_spi_c.py --program-workers diff --git a/tests/vendor/xtensa_integer/evidence/boot-with-efuse-2026-11-05.json b/tests/vendor/xtensa_integer/evidence/boot-with-efuse-2026-11-05.json index 3153985a1..0b2f104f1 100644 --- a/tests/vendor/xtensa_integer/evidence/boot-with-efuse-2026-11-05.json +++ b/tests/vendor/xtensa_integer/evidence/boot-with-efuse-2026-11-05.json @@ -37,5 +37,6 @@ "core/mcu/esp32/efuse.c": "baecfd564120f4b91c47371a9494bb7e60f2f26f60796573a74e341299df8ba8", "core/mcu/esp32/fabric.c": "553c3ac5e11e48adb71403ab3c3b5083efad942fd32cdf81c13ce7fd317075f5", "core/mcu/esp32/machine.c": "4f2b66295588c3420f9079c6fa488ca58f4c094c7b8bd17957c506b46767be51" - } + }, + "historical_scope": "Superseded synchronous-read model; ROM observes CMD=0 immediately and requests software reset. Not successful eFuse handshake; see boot-with-efuse-completion-2026-11-05.json." } diff --git a/tests/vendor/xtensa_integer/evidence/boot-with-efuse-completion-2026-11-05.json b/tests/vendor/xtensa_integer/evidence/boot-with-efuse-completion-2026-11-05.json new file mode 100644 index 000000000..254e4822f --- /dev/null +++ b/tests/vendor/xtensa_integer/evidence/boot-with-efuse-completion-2026-11-05.json @@ -0,0 +1,51 @@ +{ + "boot_verified": false, + "outcome": "blocked", + "core": "PRO only", + "rom_sha256": "9115bbcd03da9f54d3baca5ad94d588a43da54ed332347137ba3dc9010df258b", + "flash_sha256": "52fcca3c31ed835d45e22826d42dd7a7db0c867171fa330f9c56b0a81df7bee7", + "reset_vector": "0x40000400", + "prid_fixture": "0x0000cdcd", + "prid_reference": "ESP-IDF v4.4.7 xtensa_context.h documents PRO=0xcdcd, APP=0xabab; not a hardware readout", + "rom_tail_policy": "zero-fill absent DROM tail to match QEMU region setup, not silicon contents", + "flash_profile": "immutable SPI1 fixture, not a physical flash identity", + "efuse_fixture_sha256": "2d8192ea7bdf3510bc117b4ea4b833a94b2ff2ceff252ce1610c297ed633d5bb", + "efuse_completion_fixture": "one external completion opportunity every 64 instruction attempts; not physical timing", + "status": -8, + "event": 0, + "pc": "0x40000488", + "opcode": "0x136330", + "address": "0x40000488", + "fault_kind": 1, + "attempts": 441, + "efuse_completions": 3, + "retired_before_stop": 440, + "trace_tail": [ + "0x40000453", + "0x40000456", + "0x40000459", + "0x4000045c", + "0x40000471", + "0x40000474", + "0x40000477", + "0x4000047a", + "0x40000480", + "0x40000483", + "0x40000486", + "0x40000488" + ], + "fixture_note": "Explicit synthetic unprogrammed None-coded image, not owner fuse contents; externally supplied completion schedule, not physical timing", + "next_blocker": "Unsupported WSR.ATOMCTL special register 99; full native SDK boot remains unverified", + "supersedes": "boot-with-efuse-2026-11-05.json: synchronous READ completed before first ROM CMD sample and selected its software-reset failure path", + "sources_sha256": { + "core/mcu/esp32/efuse.c": "d97f8aa29a885307a17341cbeaeff2e4b4168eb9df7f8b2109cac726fb01d199", + "core/mcu/esp32/fabric.c": "ce654462a423a379753cafe7edf6278726f1e9bd131bd214c748c9a7b9021be5", + "core/mcu/esp32/machine.c": "4f2b66295588c3420f9079c6fa488ca58f4c094c7b8bd17957c506b46767be51", + "tests/vendor/xtensa_integer/probe_rom_boot.py": "324a648b6a086c7bddd6b0e37c221972667f50a55abaaac92e055de539beb623" + }, + "prid_reference_sha256": "a833937d99b2c5147e2836203643590ad2c717b2b406be31388a5a9a3c5c4aca", + "event_schedule_sensitivity": { + "period_1": "30 retired; first command already complete at ROM sample; reset request at 0x4000fdcd", + "period_128": "503 retired; three completions; same WSR.ATOMCTL blocker at 0x40000488" + } +} diff --git a/tests/vendor/xtensa_integer/probe_rom_boot.py b/tests/vendor/xtensa_integer/probe_rom_boot.py index b7f2709ac..5810211bb 100644 --- a/tests/vendor/xtensa_integer/probe_rom_boot.py +++ b/tests/vendor/xtensa_integer/probe_rom_boot.py @@ -27,8 +27,12 @@ def main(): ap=argparse.ArgumentParser(description=__doc__) ap.add_argument('--rom',type=Path,required=True);ap.add_argument('--flash',type=Path,required=True) ap.add_argument('--efuse',type=Path,help='explicit JSON list of 31 raw None-coded fuse words; not inferred board defaults') + ap.add_argument('--efuse-completion-period',type=int,default=64,help='external fixture event every N instruction attempts; not physical timing') + ap.add_argument('--prid',type=lambda value:int(value,0),default=0xcdcd,help='PRO PRID; SDK documents 0xcdcd; 0 reproduces historical invalid-ID fixture') ap.add_argument('--report',type=Path);ap.add_argument('--steps',type=int,default=100000) ap.add_argument('--cc',default='cc');args=ap.parse_args() + if not 1<=args.efuse_completion_period<=1000000:ap.error('--efuse-completion-period must be 1..1000000') + if not 0<=args.prid<=0xffffffff:ap.error('--prid must be uint32') if not 1<=args.steps<=1000000:ap.error('--steps must be 1..1000000') rom=args.rom.read_bytes();image=args.flash.read_bytes() if hashlib.sha256(rom).hexdigest()!=ROM_SHA256:ap.error('expected pinned Espressif QEMU 9.2.2 PRO ROM') @@ -50,6 +54,7 @@ def bind(name,result,*params): install=bind('esk_esp32_fabric_rom',C.c_int,pointer,C.c_uint,pointer,C.c_size_t) flash=bind('esk_esp32_fabric_flash',C.c_int,pointer,pointer,C.c_size_t) efuse=bind('esk_esp32_fabric_efuse_configure',C.c_int,pointer,C.POINTER(C.c_uint32),C.c_size_t) + complete=bind('esk_esp32_fabric_efuse_read_complete',C.c_int,pointer) spi=bind('esk_esp32_fabric_spi1_configure',C.c_int,pointer,C.c_int) core=bind('esk_esp32_machine_core',C.POINTER(Windows),pointer,C.c_uint) reset=bind('esk_xtensa_lx6_reset',C.c_int,C.POINTER(Windows),C.c_uint) @@ -61,7 +66,8 @@ def bind(name,result,*params): assert create(C.byref(allocator),C.byref(machine))==0 report={'boot_verified':False,'outcome':'step_budget_inconclusive','core':'PRO only', 'rom_sha256':ROM_SHA256,'flash_sha256':hashlib.sha256(image).hexdigest(), - 'reset_vector':'0x40000400','prid_fixture':'0x00000000 (not silicon-qualified)', + 'reset_vector':'0x40000400','prid_fixture':f'0x{args.prid:08x}', + 'prid_reference':'ESP-IDF v4.4.7 xtensa_context.h documents PRO=0xcdcd, APP=0xabab; not a hardware readout', 'rom_tail_policy':'zero-fill absent DROM tail to match QEMU region setup, not silicon contents', 'flash_profile':'immutable SPI1 fixture, not a physical flash identity'} try: @@ -77,16 +83,21 @@ def bind(name,result,*params): data=rom[0x60000:]+bytes(0x70000-len(rom));assert len(data)==65536 assert install(ram,1,C.c_char_p(data),len(data))==0 assert flash(ram,C.c_char_p(image),len(image))==0 and spi(ram,1)==0 - w=core(machine,0);assert reset(w,1)==0 and prid(w,0)==0 and run(machine,0,1)==0 + w=core(machine,0);assert reset(w,1)==0 and prid(w,args.prid)==0 and run(machine,0,1)==0 event=C.c_uint();fault=Fault();trace=deque(maxlen=12) + completions=0 + report['efuse_completion_fixture']=f'one external completion opportunity every {args.efuse_completion_period} instruction attempts; not physical timing' for attempt in range(args.steps): + if args.efuse and attempt%args.efuse_completion_period==0: + completion=complete(ram);assert completion in (0,-8) + completions+=completion==0 pc=w.contents.cpu.pc;status=step(machine,0,C.byref(event),C.byref(fault));trace.append(hex(pc)) if status or event.value>=18: report.update(outcome='blocked' if status else 'suspended',status=status,event=event.value, pc=hex(fault.pc if status else pc),opcode=hex(fault.opcode),address=hex(fault.address), fault_kind=fault.kind,attempts=attempt+1) break - report.update(retired_before_stop=retired(machine,0),trace_tail=list(trace)) + report.update(efuse_completions=completions,retired_before_stop=retired(machine,0),trace_tail=list(trace)) finally:destroy(machine) assert allocator.live_bytes==0 output=json.dumps(report,indent=2)+'\n' diff --git a/tests/vendor/xtensa_integer/run_efuse.py b/tests/vendor/xtensa_integer/run_efuse.py index 465ebe196..a50c32b41 100644 --- a/tests/vendor/xtensa_integer/run_efuse.py +++ b/tests/vendor/xtensa_integer/run_efuse.py @@ -20,7 +20,7 @@ class EFuse(C.Structure): - _fields_=[(name,C.c_uint32*31) for name in ['image','read','write']]+[(name,C.c_uint32) for name in ['enabled','clock','conf','raw','enable','dac','date']] + _fields_=[(name,C.c_uint32*31) for name in ['image','read','write']]+[(name,C.c_uint32) for name in ['enabled','clock','conf','raw','enable','dac','date','pending']] def main(): @@ -71,6 +71,8 @@ def bind(name,result,*params):f=getattr(api,name);f.restype=result;f.argtypes=li fi=bind('esk_esp32_efuse_init',None,C.POINTER(EFuse));fc=bind('esk_esp32_efuse_configure',C.c_int,C.POINTER(EFuse),C.POINTER(C.c_uint32),C.c_size_t) fr=bind('esk_esp32_efuse_read',C.c_int,C.POINTER(EFuse),C.c_uint32,C.c_uint,C.POINTER(C.c_uint32)) fw=bind('esk_esp32_efuse_write',C.c_int,C.POINTER(EFuse),C.c_uint32,C.c_uint,C.c_uint32) + complete=bind('esk_esp32_efuse_read_complete',C.c_int,C.POINTER(EFuse)) + finish=bind('esk_esp32_fabric_efuse_read_complete',C.c_int,pointer) comparisons=0;value=C.c_uint32() for words,expected in zip(images,visible): state=EFuse();fi(C.byref(state));raw=(C.c_uint32*31)(*words);assert fc(C.byref(state),raw,31)==0 @@ -81,6 +83,8 @@ def bind(name,result,*params):f=getattr(api,name);f.restype=result;f.argtypes=li comparisons+=2 assert fw(C.byref(state),address('EFUSE_CONF_REG'),4,0x5aa5)==0 assert fw(C.byref(state),address('EFUSE_CMD_REG'),4,1)==0 + assert fr(C.byref(state),address('EFUSE_CMD_REG'),4,C.byref(value))==0 and value.value==1 + assert complete(C.byref(state))==0 assert fr(C.byref(state),address('EFUSE_INT_RAW_REG'),4,C.byref(value))==0 and value.value==1 # Unchanged register header and helper bodies; only minimal MMIO glue is # supplied here, rather than linking a vendor controller implementation. @@ -124,7 +128,8 @@ def bind(name,result,*params):f=getattr(api,name);f.restype=result;f.argtypes=li states.append(w);assert run(m,cpu,1)==0 done=[False,False];event=C.c_uint();fault=Fault() for turn in range(2000): - cpu=turn%2;w=states[cpu] + cpu=0 if not done[0] else 1;w=states[cpu] + if turn%17==0:assert finish(ram) in (0,-8) if done[cpu]:continue if w.cpu.pc==0x7ffffff0: done[cpu]=True From 398857d6f9f951d22c47f2e0730401cf5ffc9db5 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 16:22:11 +0000 Subject: [PATCH 29/57] Implement Xtensa atomic control and memory-type enforcement --- CHANGELOG.md | 14 +++ core/mcu/esp32/fabric.c | 13 ++- core/mcu/esp32/machine.c | 9 +- core/mcu/xtensa/execute.h | 6 ++ core/mcu/xtensa/windows.c | 28 +++++- core/mcu/xtensa/windows.h | 8 ++ docs/STATUS.md | 5 +- docs/engineering/esp32-atomic-control.md | 99 +++++++++++++++++++ docs/engineering/esp32-boot-verification.md | 10 +- docs/engineering/esp32-dual-core.md | 4 +- docs/engineering/esp32-efuse-read-profile.md | 6 +- tests/unit/test_esp32_fabric.c | 21 +++- tests/unit/test_xtensa_atomic.c | 76 +++++++++++++- tests/unit/test_xtensa_execute.c | 4 +- tests/unit/test_xtensa_interrupts.c | 2 +- tests/unit/test_xtensa_ordering.c | 4 +- tests/unit/test_xtensa_system.c | 2 +- tests/unit/test_xtensa_windows.c | 2 +- tests/vendor/README.md | 6 ++ .../boot-with-atomctl-2026-11-05.json | 47 +++++++++ tests/vendor/xtensa_integer/run_atomctl.py | 99 +++++++++++++++++++ tests/vendor/xtensa_integer/run_execute.py | 5 +- tests/vendor/xtensa_integer/run_windows_c.py | 2 +- 23 files changed, 444 insertions(+), 28 deletions(-) create mode 100644 docs/engineering/esp32-atomic-control.md create mode 100644 tests/vendor/xtensa_integer/evidence/boot-with-atomctl-2026-11-05.json create mode 100644 tests/vendor/xtensa_integer/run_atomctl.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bddb9fc3..1f71ee901 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Original ESP32 CPU atomic control + +- Added opt-in privileged RSR/WSR/XSR.ATOMCTL, six-bit state/reset and explicit + memory-type classification for conditional stores. Local DataRAM ignores its + fields; RCW uses the indivisible bus callback; mode 0 dispatches cause 3. + Internal cache/reserved operations and unknown attributes fail unchanged. +- The native fabric supplies qualified SRAM data attributes to both CPU views. + CPU reset enables/resets ATOMCTL; legacy ABI fixtures retain explicit opt-in. +- 23,665 native atomic assertions, 3,072 GNU-assembled register comparisons and + 128 actual ESP32 C calls/2,816 instructions pass. Native real-ROM probing now + reaches 451 instructions and stops at WITLB, PC 0x400004c9. Complete memory + attributes, caches and ROM-to-SDK boot remain unverified. + + ### Original ESP32 eFuse reads and Giantec storage - Added an explicit None-coded 31-word eFuse image/read/reload profile, software diff --git a/core/mcu/esp32/fabric.c b/core/mcu/esp32/fabric.c index 9a92ac7d1..7e9370827 100644 --- a/core/mcu/esp32/fabric.c +++ b/core/mcu/esp32/fabric.c @@ -175,10 +175,19 @@ es_status esk_esp32_fabric_load(esk_esp32_fabric *f,unsigned cpu,uint32_t addr,c for(i=0;iview[cpu],addr+(uint32_t)i,&at);*at.byte=data[i]; } return ES_OK; } +static es_status atomic_type(void *ctx,uint32_t address,unsigned *type) +{ + struct view *v=(struct view *)ctx;struct location at;es_status st; + if(!v || !type || (address&3u)) return ES_EINVAL; + st=locate(v,address,&at);if(st!=ES_OK || !at.atomic) return ES_EUNSUPPORTED; + /* Qualified writable SRAM2/SRAM1 lies inside the core's configured + * DataRAM0 window. No external cacheability/PSRAM type is guessed. */ + *type=ESK_XT_MEM_LOCAL;return ES_OK; +} esk_xtensa_memory esk_esp32_fabric_bus(esk_esp32_fabric *f,unsigned cpu) { - esk_xtensa_memory bus={NULL,NULL,NULL,NULL,NULL}; - if(f && cpu<2) { bus.ctx=&f->view[cpu];bus.read=read_bus;bus.write=write_bus;bus.compare_exchange=cas_bus;bus.fetch=fetch_bus; } + esk_xtensa_memory bus={NULL,NULL,NULL,NULL,NULL,NULL}; + if(f && cpu<2) { bus.ctx=&f->view[cpu];bus.read=read_bus;bus.write=write_bus;bus.compare_exchange=cas_bus;bus.fetch=fetch_bus;bus.atomic_type=atomic_type; } return bus; } diff --git a/core/mcu/esp32/machine.c b/core/mcu/esp32/machine.c index 1bb2c4774..bf2079639 100644 --- a/core/mcu/esp32/machine.c +++ b/core/mcu/esp32/machine.c @@ -99,10 +99,15 @@ static es_status bus_cas(void *ctx,uint32_t address,uint32_t expected,uint32_t d struct machine_view *v=(struct machine_view *)ctx;esk_xtensa_memory ram=esk_esp32_fabric_bus(v->m->fabric,v->cpu); return ram.compare_exchange(ram.ctx,address,expected,desired,observed); } +static es_status bus_atomic_type(void *ctx,uint32_t address,unsigned *type) +{ + struct machine_view *v=(struct machine_view *)ctx;esk_xtensa_memory ram=esk_esp32_fabric_bus(v->m->fabric,v->cpu); + return ram.atomic_type(ram.ctx,address,type); +} esk_xtensa_memory esk_esp32_machine_bus(esk_esp32_machine *m,unsigned cpu) { - esk_xtensa_memory bus={NULL,NULL,NULL,NULL,NULL}; - if(m && cpu<2) { bus.ctx=&m->view[cpu];bus.read=bus_read;bus.write=bus_write;bus.fetch=bus_fetch;bus.compare_exchange=bus_cas; } + esk_xtensa_memory bus={NULL,NULL,NULL,NULL,NULL,NULL}; + if(m && cpu<2) { bus.ctx=&m->view[cpu];bus.read=bus_read;bus.write=bus_write;bus.fetch=bus_fetch;bus.atomic_type=bus_atomic_type;bus.compare_exchange=bus_cas; } return bus; } es_status esk_esp32_machine_source(esk_esp32_machine *m,unsigned source,int asserted) diff --git a/core/mcu/xtensa/execute.h b/core/mcu/xtensa/execute.h index b9179fc51..4ce68c4a4 100644 --- a/core/mcu/xtensa/execute.h +++ b/core/mcu/xtensa/execute.h @@ -16,6 +16,7 @@ * Callbacks must not re-enter or mutate the executing CPU state. Queue its * input/clock changes for instruction boundaries; these APIs are not thread-safe. */ typedef struct esk_xtensa_state { uint32_t a[16], pc; } esk_xtensa_state; +enum { ESK_XT_MEM_LOCAL, ESK_XT_MEM_BYPASS, ESK_XT_MEM_WRITETHROUGH, ESK_XT_MEM_WRITEBACK }; typedef struct esk_xtensa_memory { void *ctx; es_status (*read)(void *ctx, uint32_t address, unsigned bytes, uint32_t *value); @@ -26,6 +27,11 @@ typedef struct esk_xtensa_memory { es_status (*compare_exchange)(void *ctx,uint32_t address,uint32_t expected,uint32_t desired,uint32_t *observed); /* Optional instruction-bus access. Null uses read for legacy flat test buses. */ es_status (*fetch)(void *ctx,uint32_t address,unsigned bytes,uint32_t *value); + /* Optional pure classification for ATOMCTL-enabled conditional stores. + * Must classify the complete aligned word without an access/side effect. + * LOCAL bypasses ATOMCTL; other values select its BY/WT/WB field. + * Unknown attributes must fail, never silently select bypass. */ + es_status (*atomic_type)(void *ctx,uint32_t address,unsigned *type); } esk_xtensa_memory; enum { ESK_XT_OK, ESK_XT_UNSUPPORTED, ESK_XT_TRUNCATED, ESK_XT_FETCH, ESK_XT_ALIGN, ESK_XT_LOAD, ESK_XT_STORE, ESK_XT_DIV_ZERO, ESK_XT_ATOMIC }; diff --git a/core/mcu/xtensa/windows.c b/core/mcu/xtensa/windows.c index 76b9688a3..dd2b14e75 100644 --- a/core/mcu/xtensa/windows.c +++ b/core/mcu/xtensa/windows.c @@ -31,6 +31,11 @@ void esk_xtensa_windows_init(esk_xtensa_windows *w, uint32_t pc, const uint32_t memset(w,0,sizeof *w); w->cpu.pc=pc; w->ps=ESK_XT_PS_WOE; w->start=1; if (vectors) memcpy(w->vectors,vectors,sizeof w->vectors); } +es_status esk_xtensa_atomctl_configure(esk_xtensa_windows *w) +{ + if(!w) return ES_EINVAL; + w->atomctl=0x28;w->atomctl_enabled=1;return ES_OK; +} es_status esk_xtensa_prid_configure(esk_xtensa_windows *w,uint32_t value) { if(!w) return ES_EINVAL; @@ -49,6 +54,7 @@ es_status esk_xtensa_vecbase_configure(esk_xtensa_windows *w,uint32_t base) es_status esk_xtensa_lx6_reset(esk_xtensa_windows *w,unsigned vector_input) { if(!w || vector_input>1) return ES_EINVAL; + (void)esk_xtensa_atomctl_configure(w); save(w);w->base&=15u;w->start&=65535u; w->ps=0x1f;w->lcount=0;w->cpu.pc=vector_input ? 0x40000400u : 0x50000000u; w->exceptions_enabled=1;w->irq.enabled=1;w->irq.waiting=0; @@ -83,7 +89,7 @@ es_status esk_xtensa_raise_exception(esk_xtensa_windows *w, unsigned cause, } static int special_valid(const esk_xtensa_windows *w,unsigned reg) { - return (w->irq.enabled && esk_xtensa_irq_special(reg)) || reg<=3 || reg==12 || reg==72 || reg==73 || reg==177 || reg==192 || reg==230 || (reg==231 && w->vecbase_enabled) || reg==232 || reg==238 || (reg>=244 && reg<=247) || (reg==235 && w->prid_enabled); + return (w->irq.enabled && esk_xtensa_irq_special(reg)) || (reg==99 && w->atomctl_enabled) || reg<=3 || reg==12 || reg==72 || reg==73 || reg==177 || reg==192 || reg==230 || (reg==231 && w->vecbase_enabled) || reg==232 || reg==238 || (reg>=244 && reg<=247) || (reg==235 && w->prid_enabled); } static uint32_t special_read(const esk_xtensa_windows *w,unsigned reg) { @@ -91,7 +97,7 @@ static uint32_t special_read(const esk_xtensa_windows *w,unsigned reg) if (esk_xtensa_irq_special(reg)) { uint32_t value=0; (void)esk_xtensa_irq_read(w,reg,&value); return value; } switch (reg) { case 0:return w->lbeg; case 1:return w->lend; case 2:return w->lcount; case 3:return w->sar; - case 12:return w->scompare1; case 72:return w->base; case 73:return w->start; case 177:return w->epc1; + case 99:return w->atomctl; case 12:return w->scompare1; case 72:return w->base; case 73:return w->start; case 177:return w->epc1; case 192:return w->depc; case 230:return w->ps&ESK_XT_PS_MASK; case 235:return w->prid; case 231:return w->vecbase; case 232:return w->exccause; default:return w->excvaddr; } } @@ -102,6 +108,7 @@ static void special_write(esk_xtensa_windows *w,unsigned reg,uint32_t value) switch (reg) { case 0:w->lbeg=value;break; case 1:w->lend=value;break; case 2:w->lcount=value;break; case 3:w->sar=value&63u;break; + case 99:w->atomctl=value;break; case 12:w->scompare1=value;break; case 72:w->base=value&15u;load(w);break; case 73:w->start=value&65535u;break; @@ -180,7 +187,7 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo else if ((word&UINT32_C(0xffff0f))==0x408000) kind=XT_ROTW; else if ((word&UINT32_C(0xff000f))==0x030000 || (word&UINT32_C(0xff000f))==0x130000 || (word&UINT32_C(0xff000f))==0x610000) { unsigned reg=(word>>8)&255u; - if (!special_valid(&next,reg) || (reg==227 && (word&UINT32_C(0xff000f))==0x030000) || ((word&UINT32_C(0xff000f))==0x610000 && reg!=3 && reg!=12 && reg!=230 && reg!=231 && !(reg>=244 && reg<=247)) || (reg==235 && (word&UINT32_C(0xff000f))!=0x030000)) + if (!special_valid(&next,reg) || (reg==227 && (word&UINT32_C(0xff000f))==0x030000) || ((word&UINT32_C(0xff000f))==0x610000 && reg!=3 && reg!=12 && reg!=230 && reg!=231 && reg!=99 && !(reg>=244 && reg<=247)) || (reg==235 && (word&UINT32_C(0xff000f))!=0x030000)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); kind=XT_SPECIAL; mask=bit(t); } @@ -260,7 +267,7 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo uint32_t form=word&UINT32_C(0xff000f); if (form==0x030000) next.cpu.a[t]=old; else { - if(reg==231 && (value&1023u)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + if((reg==99 && (value&~UINT32_C(63))) || (reg==231 && (value&1023u))) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); special_write(&next,reg,value);if(form==0x610000) next.cpu.a[t]=old; } next.cpu.pc=pc+3; @@ -313,6 +320,19 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo } else if (kind==XT_ATOMIC) { uint32_t address=next.cpu.a[s]+((word>>16)<<2),observed=0; if(address&3u) return reject(fault,ESK_XT_ALIGN,pc,address,word); + if(next.atomctl_enabled) { + unsigned type=0,mode; + if(!memory || !memory->atomic_type || memory->atomic_type(memory->ctx,address,&type)!=ES_OK || type>ESK_XT_MEM_WRITEBACK) + return reject(fault,ESK_XT_UNSUPPORTED,pc,address,word); + if(type!=ESK_XT_MEM_LOCAL) { + mode=(next.atomctl>>(2u*(type-1u)))&3u; + if(!mode) { + if(!next.exceptions_enabled) return reject(fault,ESK_XT_ATOMIC,pc,address,word); + st=esk_xtensa_raise_exception(&next,3,address,1,event);if(st==ES_OK) *w=next;return st; + } + if(mode!=1) return reject(fault,ESK_XT_UNSUPPORTED,pc,address,word); + } + } if(!memory || !memory->compare_exchange) return reject(fault,ESK_XT_UNSUPPORTED,pc,address,word); st=memory->compare_exchange(memory->ctx,address,next.scompare1,next.cpu.a[t],&observed); if(st!=ES_OK) return reject(fault,st==ES_EUNSUPPORTED ? ESK_XT_UNSUPPORTED : ESK_XT_ATOMIC,pc,address,word); diff --git a/core/mcu/xtensa/windows.h b/core/mcu/xtensa/windows.h index f079f1794..e94876978 100644 --- a/core/mcu/xtensa/windows.h +++ b/core/mcu/xtensa/windows.h @@ -28,6 +28,7 @@ typedef struct esk_xtensa_windows { uint32_t vectors[6]; /* overflow4, underflow4, overflow8, underflow8, overflow12, underflow12 */ uint32_t vecbase,vecbase_enabled; uint32_t threadptr,misc[4],prid,prid_enabled; + uint32_t atomctl,atomctl_enabled; } esk_xtensa_windows; /* event 0: retired; 1..6: window transition; 7/8/9: kernel/user/double exception; * 11..17: accepted interrupt level 1..7; 18: suspended WAITI (step only). @@ -42,6 +43,13 @@ typedef struct esk_xtensa_windows { * configuration. This supplies a constant board/SoC input, not a guessed ESP32 * silicon ID or a live pin-waveform model; it survives supported CPU reset. */ es_status esk_xtensa_prid_configure(esk_xtensa_windows *state,uint32_t value); +/* Opt-in conditional-store ATOMCTL SR99, six bits, reset 0x28. + * RSR/WSR/XSR privileged; reserved upper-bit writes fail unchanged. + * S32C1I requires bus atomic_type: LOCAL ignores fields; 0 raises cause 3, + * 1 uses indivisible RCW callback; internal cache (2) / reserved (3) fail. + * Cache/internal-operation, Exclusive Access, bus waveforms/timing unqualified. + * Legacy ABI fixtures remain disabled until opt-in; CPU reset enables it. */ +es_status esk_xtensa_atomctl_configure(esk_xtensa_windows *state); /* Explicit ESP32 relocatable-vector profile. Only 1024-aligned bases are * qualified; unaligned/lock-bit writes fail unchanged rather than guessing * silicon masking. Aligned RSR/WSR/XSR.VECBASE use SR231 when enabled. diff --git a/docs/STATUS.md b/docs/STATUS.md index 9ac9bffa0..970df7f21 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -10,7 +10,10 @@ does not mean the described behaviour exists. **Current implementation update:** an [explicit eFuse read/reload profile](engineering/esp32-efuse-read-profile.md) now supports None-coded input images, read protection and completion routing. With a supplied synthetic unprogrammed image, real ROM execution advances from -seven to 440 retired instructions, then blocks on WSR.ATOMCTL at PC 0x40000488. +seven to 451 retired instructions, then blocks on WITLB at PC 0x400004c9. +[ATOMCTL](engineering/esp32-atomic-control.md) now has six-bit privileged register +access, reset, memory-type selection, local/RCW atomics and cause-3 dispatch. +Internal cache operations and full instruction/data attributes remain absent. Read completion is an explicit external event; no physical duration is inferred. The earlier synchronous profile took the ROM failure/reset path. The owner's actual fuse image is not inferred. A separate diff --git a/docs/engineering/esp32-atomic-control.md b/docs/engineering/esp32-atomic-control.md new file mode 100644 index 000000000..182c3798c --- /dev/null +++ b/docs/engineering/esp32-atomic-control.md @@ -0,0 +1,99 @@ +# ESP32 CPU atomic-operation control + +**Implemented: opt-in six-bit ATOMCTL register access, local-data-RAM and RCW +conditional stores, and configured LoadStoreError exceptions. Internal cache +operations, complete memory attributes/MMU and native SDK boot remain absent.** + +## Contract and real operation + +Xtensa ISA RI-2021.8 section 4.3.13 and table 190 define conditional-store +ATOMCTL, privileged special register 99. It resets to 0x28 and supports RSR, +WSR and XSR. Original ESP32 core configuration enables S32C1I. This profile +implements its six low bits; Exclusive Access's bit 8 is not implemented. +Writes with upper/reserved bits set fail unchanged because their effects are +undefined for this profile. All six low bits read back, including field values +whose conditional-store operation is reserved or unsupported. + +| Field | Bits | Value 0 | Value 1 | Value 2 | Value 3 | +|---|---|---|---|---|---| +| BY, bypass | 1:0 | LoadStoreError | bus RCW | reserved | reserved | +| WT, writethrough | 3:2 | LoadStoreError | bus RCW | internal cache, unqualified | reserved | +| WB, writeback | 5:4 | LoadStoreError | bus RCW | internal cache, unqualified | reserved | + +Local DataRAM ignores these fields and performs the atomic operation directly. +Register 0x15 selects RCW for all three external memory types. Reset 0x28 selects +internal operation for WT/WB and an exception for BY; it does not grant arbitrary +atomic access to all memory. Unimplemented cache operations and reserved +operations fail explicitly, rather than being implemented as a bus transaction. + +`esk_xtensa_atomctl_configure` enables the profile with reset value 0x28. +Supported original-ESP32 CPU reset also enables/resets it. Existing ABI-only +fixtures remain disabled until explicit opt-in; they retain their previously +qualified direct conditional-exchange contract. Register state is per CPU. +Privileged access raises cause 8 through the configured exception path. Window +checks still precede any conditional-store bus activity. No new clock-cycle, +cache, pipeline or synchronization-delay claim is made. + +## Memory provider and observable access + +`esk_xtensa_memory.atomic_type` classifies the complete aligned word without +performing a load/store. An enabled profile requires this callback; missing, +unknown or invalid classification fails unchanged. The provider supplies LOCAL, +BYPASS, WRITETHROUGH or WRITEBACK; the CPU never infers cacheability from an +unimplemented MMU or supplies a permissive fallback. + +LOCAL and RCW use the provider's indivisible compare/exchange callback, returning +the original word on match or mismatch. There is no ordinary read/write fallback. +Mode 0 raises LoadStoreError cause 3, recording EXCVADDR and the faulting PC, +without invoking compare/exchange or advancing the instruction. Kernel/user/ +double dispatch follows the existing exception profile. Without that profile it +returns the explicit atomic fault. Internal-cache and reserved modes fail without +changing CPU or memory state. Existing alignment-fault dispatch limitations still +apply; this is not qualification of every CPU memory exception or priority. + +The native ESP32 fabric classifies its qualified writable SRAM1/SRAM2 data windows +as LOCAL. They lie inside DataRAM0 in the pinned original-ESP32 core configuration +(0x3ff80000, size 524288). Instruction aliases, RTC memories, MMIO, external +flash/PSRAM and holes remain unsupported atomic targets; their attributes or +atomic interconnect support are not guessed. The machine forwards classification +through the same core-specific bus view used for accesses. + +## Evidence + +- `test_xtensa_atomic`: **23,665 assertions**, including all six-bit register + values and all operand registers/forms, reserved-upper-bit rejection, reset, + privilege, local bypass, all BY/WT/WB field combinations, matched/mismatched + RCW, cause-3 entry and unchanged unsupported/missing-classification failures. +- Fabric tests cover both cores' SRAM endpoints and explicitly rejected targets. +- `run_atomctl.py`: **3,072 independent GNU-assembled register comparisons**. + Actual ESP32 GCC then compiles a C function that writes/reads ATOMCTL, performs + S32C1I on native shared SRAM, checks the original/current value and restores + the register. All 64 control values on both cores pass: **128 calls and 2,816 + retired instructions**. This checks that LOCAL ignores external type fields, + including zero/reserved modes. The bus is native, with no Python memory + callbacks. Host ABI function setup is explicit; this is not application boot. + +The real-ROM probe now retires **451 instructions**, including WSR.ATOMCTL=0x15, +then stops at unsupported **WITLB**, opcode 0x506340, PC 0x400004c9. The +[recorded probe](../../tests/vendor/xtensa_integer/evidence/boot-with-atomctl-2026-11-05.json) +uses the same external ROM, actual SDK image, synthetic None-coded eFuse image, +PRO PRID and explicit read-completion schedule documented in the +[boot investigation](esp32-boot-verification.md). No PC shortcut or unknown-MMIO +fallback is introduced. WITLB/memory-attribute behavior is the next observed +blocker, not the only remaining requirement for complete boot. + +## References and reproduction + +External Xtensa ISA summary RI-2021.8, sections 4.3.13 and table 190; PDF SHA-256 +`474c4aaf2d429ff79c48127e738c4110cecd8072133169ad6d00a050edf244b7`. +No manual text or vendor implementation is included in runtime source. +ESP32 `core-isa.h` toolchain pin is recorded in the +[target investigation](esp32-dual-core.md). + +```sh +python3 tests/vendor/xtensa_integer/run_atomctl.py \ + --assembler /path/to/xtensa-esp32-elf-as \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld --nm /path/to/xtensa-esp32-elf-nm +``` diff --git a/docs/engineering/esp32-boot-verification.md b/docs/engineering/esp32-boot-verification.md index 32b52a03c..5b3b5b88f 100644 --- a/docs/engineering/esp32-boot-verification.md +++ b/docs/engineering/esp32-boot-verification.md @@ -3,7 +3,7 @@ **Result on 2026-11-05: the owner's board reports ESP32-D0WD-V3 revision 3.1, JEDEC C4 60 16 and 4 MB flash. Giantec's GT25Q32A-U datasheet matches that ID; exact package/ordering suffix remains unconfirmed. The pinned SDK example boots -in Espressif QEMU. Native EmbSIM stops at unsupported WSR.ATOMCTL after 440 retired +in Espressif QEMU. Native EmbSIM stops at unsupported WITLB after 451 retired instructions with the new explicit synthetic eFuse image; without that profile, the baseline stops at an eFuse read after seven.** Neither result establishes successful native boot. @@ -114,7 +114,11 @@ configuration or printed SDK capacity must not substitute for hardware identity. ## Native EmbSIM result: blocked -**Implementation follow-up:** the new [eFuse read profile](esp32-efuse-read-profile.md) +**Latest follow-up:** [ATOMCTL](esp32-atomic-control.md) now passes the former +CPU-register blocker. The same explicit-input probe reaches 451 retirements and +unsupported WITLB at PC 0x400004c9. Native SDK boot remains blocked. + +**Prior eFuse implementation result:** the new [eFuse read profile](esp32-efuse-read-profile.md) accepts an explicitly supplied image. The synthetic unprogrammed fixture advances the same ROM to 440 retired instructions and an unsupported WSR.ATOMCTL at PC 0x40000488, with explicitly supplied controller-completion events. That is not the owner's factory image and still does not @@ -137,7 +141,7 @@ fault load failure (kind 5), status -1 Pinned SDK `soc/esp32/include/soc/efuse_reg.h` identifies that address as `EFUSE_BLK0_RDATA0_REG`. This baseline deliberately has no eFuse profile attached. With the new explicit image attached, the first observed blocker -moves to WSR.ATOMCTL, as recorded above. The earlier synchronous profile took +moved first to WSR.ATOMCTL and now to WITLB, as recorded above. The earlier synchronous profile took the ROM failure path and requested a software reset through RTC options; it did not pass the eFuse handshake. RTC/strapping/reset state, further eFuse behavior, dedicated flash diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 26b055e65..a3e79de6e 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -29,7 +29,9 @@ and differs from the separate Winbond test profile. The [explicit eFuse read/reload profile](esp32-efuse-read-profile.md) now handles that first access when a caller supplies all 31 uncoded words. A synthetic unprogrammed fixture with explicit read-completion events advances ROM to 440 -retired instructions and unsupported WSR.ATOMCTL at PC 0x40000488. It is not the owner's fuse dump and does +retired instructions in the prior profile. With +[atomic-operation control](esp32-atomic-control.md), it now reaches 451 instructions +and unsupported WITLB at PC 0x400004c9. It is not the owner's fuse dump and does not establish full boot. The [Giantec storage subset](esp32-giantec-profile.md) adds independently checked timing/WEL behavior for the matching JEDEC family; complete chip behavior remains unqualified. diff --git a/docs/engineering/esp32-efuse-read-profile.md b/docs/engineering/esp32-efuse-read-profile.md index 2ba8b1f68..a765ecf52 100644 --- a/docs/engineering/esp32-efuse-read-profile.md +++ b/docs/engineering/esp32-efuse-read-profile.md @@ -72,7 +72,11 @@ header/helper extraction retains Espressif's Apache-2.0 notice. Synthetic fuse images contain no owner's keys, MAC or calibration. This is selected helper acceptance, not the full burn stack or SDK boot. -## Real ROM probe and next blocker +## Real ROM probe and subsequent blockers + +The subsequent [ATOMCTL implementation](esp32-atomic-control.md) now advances +this probe to WITLB at PC 0x400004c9 after 451 retirements. The historical +eFuse-only result below retains the previous source/profile scope. The previous no-image probe still stops at eFuse after seven retired instructions. With the explicitly selected diff --git a/tests/unit/test_esp32_fabric.c b/tests/unit/test_esp32_fabric.c index 906dba63b..cef0f69ce 100644 --- a/tests/unit/test_esp32_fabric.c +++ b/tests/unit/test_esp32_fabric.c @@ -104,7 +104,26 @@ static void allocation_failures(void) CHECK_EQ(bus.fetch(bus.ctx,0x40000000,4,&v),ES_OK);CHECK_EQ(v,0x67676767); esk_esp32_fabric_destroy(f);free(rom);CHECK_EQ(a.live_bytes,0); } +static void atomic_memory_attributes(void) +{ + esk_alloc a;esk_esp32_fabric *f=NULL;esk_xtensa_memory bus;unsigned cpu,i,type=99; + const uint32_t local[]={0x3ffae000,0x3ffdfffc,0x3ffe0000,0x3ffffffc}; + const uint32_t unknown[]={0x3ff80000,0x40080000,0x400a0000,0x3f400000,0x3f800000,0x50000000,0x3ff5a000}; + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_fabric_create(&a,&f),ES_OK); + for(cpu=0;cpu<2;cpu++) { + bus=esk_esp32_fabric_bus(f,cpu); + for(i=0;ireads++;*value=b->value;return ES_OK; @@ -30,7 +30,7 @@ static es_status execute(esk_xtensa_windows *w,const esk_xtensa_memory *bus,uint } static void compare_and_alias(void) { - bus_state b={0};esk_xtensa_memory bus={&b,read_mem,write_mem,cas,NULL}; + bus_state b={0};esk_xtensa_memory bus={&b,read_mem,write_mem,cas,NULL,NULL}; esk_xtensa_windows one,two;unsigned event;esk_xtensa_fault f; esk_xtensa_windows_init(&one,0x40000000,NULL);esk_xtensa_windows_init(&two,0x40001000,NULL); one.cpu.a[3]=256;one.cpu.a[2]=1;two.cpu.a[3]=256;two.cpu.a[2]=2; @@ -50,7 +50,7 @@ static void compare_and_alias(void) } static void failures_and_window_priority(void) { - bus_state b={9,0,0,0,0,0,0,0};esk_xtensa_memory bus={&b,read_mem,write_mem,cas,NULL}; + bus_state b={9,0,0,0,0,0,0,0,0,0,0};esk_xtensa_memory bus={&b,read_mem,write_mem,cas,NULL,NULL}; esk_xtensa_windows w,before;unsigned event,calls;esk_xtensa_fault f; esk_xtensa_windows_init(&w,0x40000000,NULL);w.cpu.a[3]=257;w.cpu.a[2]=5;w.scompare1=9;before=w; CHECK_EQ(execute(&w,&bus,0x00e322,&event,&f),ES_EINVAL);CHECK_EQ(f.kind,ESK_XT_ALIGN); @@ -74,6 +74,74 @@ static void special_register(void) CHECK_EQ(execute(&w,NULL,0x030c20,&event,&f),ES_OK);CHECK_EQ(w.cpu.a[2],0x12345678u); w.cpu.a[4]=9;CHECK_EQ(execute(&w,NULL,0x610c40,&event,&f),ES_OK);CHECK_EQ(w.scompare1,9);CHECK_EQ(w.cpu.a[4],0x12345678u); } +static es_status memory_type(void *ctx,uint32_t address,unsigned *type) +{ + bus_state *b=(bus_state *)ctx;(void)address;b->typecalls++; + if(b->typefail) return ES_EUNSUPPORTED; + *type=b->type;return ES_OK; +} +static void atomctl_register_and_reset(void) +{ + unsigned reg,form,value,event;esk_xtensa_fault fault;esk_xtensa_windows w,before; + const uint32_t forms[3]={0x036300,0x136300,0x616300}; + CHECK_EQ(esk_xtensa_atomctl_configure(NULL),ES_EINVAL); + esk_xtensa_windows_init(&w,0x40080000,NULL);before=w; + CHECK_EQ(execute(&w,NULL,forms[0],&event,&fault),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&w,&before,sizeof w),0); + for(reg=0;reg<16;reg++) for(form=0;form<3;form++) for(value=0;value<64;value++) { + esk_xtensa_windows_init(&w,0x40080000,NULL);CHECK_EQ(esk_xtensa_atomctl_configure(&w),ES_OK); + CHECK_EQ(w.atomctl,0x28);w.atomctl=63-value;w.cpu.a[reg]=value; + CHECK_EQ(execute(&w,NULL,forms[form]|(reg<<4),&event,&fault),ES_OK); + CHECK_EQ(w.atomctl,form ? value : 63-value);CHECK_EQ(w.cpu.a[reg],form==1 ? value : 63-value); + } + for(value=6;value<32;value++) { + esk_xtensa_windows_init(&w,0x40080000,NULL);CHECK_EQ(esk_xtensa_atomctl_configure(&w),ES_OK); + w.cpu.a[3]=UINT32_C(1)<>(2u*(type-1u)))&3u : 1u; + memset(&b,0,sizeof b);b.type=type;b.value=matched ? 9u : 8u; + esk_xtensa_windows_init(&w,0x40080000,NULL);CHECK_EQ(esk_xtensa_lx6_reset(&w,1),ES_OK); + w.cpu.pc=0x40080000;w.ps=0;w.cpu.a[3]=256;w.cpu.a[2]=42;w.scompare1=9;w.atomctl=control;before=w; + if(mode==1) { + CHECK_EQ(execute(&w,&bus,0x00e322,&event,&fault),ES_OK);CHECK_EQ(event,0); + CHECK_EQ(b.calls,1);CHECK_EQ(w.cpu.a[2],matched ? 9 : 8);CHECK_EQ(b.value,matched ? 42 : 8); + } else if(!mode) { + CHECK_EQ(execute(&w,&bus,0x00e322,&event,&fault),ES_OK);CHECK_EQ(event,7);CHECK_EQ(b.calls,0); + CHECK_EQ(w.exccause,3);CHECK_EQ(w.excvaddr,256);CHECK_EQ(w.epc1,before.cpu.pc);CHECK_EQ(w.cpu.a[2],42); + } else { + CHECK_EQ(execute(&w,&bus,0x00e322,&event,&fault),ES_EUNSUPPORTED);CHECK_EQ(b.calls,0);CHECK_EQ(memcmp(&w,&before,sizeof w),0); + } + CHECK_EQ(b.typecalls,1);CHECK_EQ(b.reads,0);CHECK_EQ(b.writes,0); + } + esk_xtensa_windows_init(&w,0x40080000,NULL);CHECK_EQ(esk_xtensa_atomctl_configure(&w),ES_OK); + w.cpu.a[3]=256;w.cpu.a[2]=42;w.atomctl=0x15;before=w;b.calls=0;b.typefail=1; + CHECK_EQ(execute(&w,&bus,0x00e322,&event,&fault),ES_EUNSUPPORTED);CHECK_EQ(b.calls,0);CHECK_EQ(memcmp(&w,&before,sizeof w),0); + b.typefail=0;b.type=4; + CHECK_EQ(execute(&w,&bus,0x00e322,&event,&fault),ES_EUNSUPPORTED);CHECK_EQ(b.calls,0);CHECK_EQ(memcmp(&w,&before,sizeof w),0); + b.type=ESK_XT_MEM_BYPASS;w.atomctl=0;before=w; + CHECK_EQ(execute(&w,&bus,0x00e322,&event,&fault),ES_EINVAL);CHECK_EQ(fault.kind,ESK_XT_ATOMIC);CHECK_EQ(b.calls,0);CHECK_EQ(memcmp(&w,&before,sizeof w),0); + for(type=0;type<3;type++) { + CHECK_EQ(esk_xtensa_lx6_reset(&w,1),ES_OK);w.ps=type==2 ? ESK_XT_PS_EXCM : type==1 ? 32u : 0u; + w.cpu.pc=0x40080000;w.atomctl=0;w.cpu.a[3]=256; + CHECK_EQ(execute(&w,&bus,0x00e322,&event,&fault),ES_OK);CHECK_EQ(event,7+type); + CHECK_EQ(w.exccause,3);CHECK_EQ(w.excvaddr,256);CHECK_EQ(b.calls,0); + CHECK_EQ(w.cpu.pc,0x40000300u+(type==2 ? 0xc0u : type*0x40u)); + } + before=w;bus.atomic_type=NULL; + CHECK_EQ(execute(&w,&bus,0x00e322,&event,&fault),ES_EUNSUPPORTED);CHECK_EQ(b.calls,0);CHECK_EQ(memcmp(&w,&before,sizeof w),0); +} TEST_MAIN_BEGIN - (void)t_str_eq;RUN(compare_and_alias);RUN(failures_and_window_priority);RUN(special_register); + (void)t_str_eq;RUN(compare_and_alias);RUN(failures_and_window_priority);RUN(special_register);RUN(atomctl_register_and_reset);RUN(atomctl_memory_policy); TEST_MAIN_END diff --git a/tests/unit/test_xtensa_execute.c b/tests/unit/test_xtensa_execute.c index abd02ecfa..67725e927 100644 --- a/tests/unit/test_xtensa_execute.c +++ b/tests/unit/test_xtensa_execute.c @@ -42,7 +42,7 @@ static void program(void) 0x4b,0x22, 0x0b,0x33, 0x56,0x43,0xff, 0x45,0,0, 0xc6,0,0, 0x4a,0x44, 0x0d,0xf0, 0x3d,0xf0 }; - memory m = {{0},0,0,0,0}; esk_xtensa_memory bus = {&m,read_mem,write_mem,NULL,NULL}; + memory m = {{0},0,0,0,0}; esk_xtensa_memory bus = {&m,read_mem,write_mem,NULL,NULL,NULL}; esk_xtensa_state s = {{0},0}; esk_xtensa_fault f; unsigned i, steps = 0; memcpy(m.data, code, sizeof code); for (i = 0; i < 4; i++) m.data[256+i*4] = (unsigned char)(i+1); @@ -53,7 +53,7 @@ static void program(void) } static void memory_and_faults(void) { - memory m = {{0},0,0,0,0}; esk_xtensa_memory bus = {&m,read_mem,write_mem,NULL,NULL}; + memory m = {{0},0,0,0,0}; esk_xtensa_memory bus = {&m,read_mem,write_mem,NULL,NULL,NULL}; esk_xtensa_state s = {{0},0}, before; esk_xtensa_fault f; s.a[2]=UINT32_C(0x89abcdef); s.a[3]=256; CHECK_EQ(execute(&s,&bus,0x006322,3,&f),ES_OK); /* s32i a2,a3,0 */ diff --git a/tests/unit/test_xtensa_interrupts.c b/tests/unit/test_xtensa_interrupts.c index 0d97d3945..e01132abf 100644 --- a/tests/unit/test_xtensa_interrupts.c +++ b/tests/unit/test_xtensa_interrupts.c @@ -138,7 +138,7 @@ static void fetched_handler(void) /* Save A2 in EXCSAVE3, acknowledge software input 29, restore A2, RFI 3. */ static const unsigned char handler[]={0x20,0xd3,0x13,0x22,0xa0,1,0x30,0x22,1,0x20,0xe3,0x13,0x20,0xd3,3,0x10,0x33,0}; static const uint32_t h[6]={0x1080,0x10c0,0x1100,0x1140,0x1180,0x11c0}; - unsigned char data[512]={0};esk_xtensa_memory bus={data,code_read,NULL,NULL,NULL};esk_xtensa_windows w; + unsigned char data[512]={0};esk_xtensa_memory bus={data,code_read,NULL,NULL,NULL,NULL};esk_xtensa_windows w; esk_xtensa_fault f;unsigned event,steps=0,entries=0; memcpy(data,program,sizeof program);memcpy(data+192,handler,sizeof handler); init(&w);w.cpu.pc=0x1000;CHECK_EQ(esk_xtensa_irq_configure(&w,h),ES_OK); diff --git a/tests/unit/test_xtensa_ordering.c b/tests/unit/test_xtensa_ordering.c index cc92b40ce..94a8a6dd1 100644 --- a/tests/unit/test_xtensa_ordering.c +++ b/tests/unit/test_xtensa_ordering.c @@ -26,7 +26,7 @@ static void registers_offsets_and_faults(void) unsigned t,s,k,store,event;esk_xtensa_fault fault; for(t=0;t<16;t++) for(s=0;s<16;s++) for(k=0;k<3;k++) for(store=0;store<2;store++) { esk_xtensa_windows w,before;fixture f={0,0x89abcdef,0,0,0};uint32_t expected,address; - esk_xtensa_memory bus={&f,read_word,write_word,NULL,NULL}; + esk_xtensa_memory bus={&f,read_word,write_word,NULL,NULL,NULL}; unsigned offset=k==2 ? 255u : k; esk_xtensa_windows_init(&w,0x40080000,NULL); w.cpu.a[t]=0x12345678;w.cpu.a[s]=0x3ffb0000; @@ -52,7 +52,7 @@ static void window_priority(void) unsigned store,event;esk_xtensa_fault fault; uint32_t vectors[6]={0x40081000,0x40081100,0x40081200,0x40081300,0x40081400,0x40081500}; for(store=0;store<2;store++) { - esk_xtensa_windows w;fixture f={0,0,0,0,0};esk_xtensa_memory bus={&f,read_word,write_word,NULL,NULL}; + esk_xtensa_windows w;fixture f={0,0,0,0,0};esk_xtensa_memory bus={&f,read_word,write_word,NULL,NULL,NULL}; esk_xtensa_windows_init(&w,0x40080000,vectors);w.start=7; CHECK_EQ(execute(&w,&bus,8,3,0,(int)store,&event,&fault),ES_OK); CHECK_EQ(event,1);CHECK_EQ(w.cpu.pc,vectors[0]);CHECK_EQ(f.reads,0);CHECK_EQ(f.writes,0); diff --git a/tests/unit/test_xtensa_system.c b/tests/unit/test_xtensa_system.c index 726784bdb..a52bcc850 100644 --- a/tests/unit/test_xtensa_system.c +++ b/tests/unit/test_xtensa_system.c @@ -115,7 +115,7 @@ static void fetched_syscall_program(void) static const unsigned char program[]={0x0c,0x72,0,0x50,0,0x1b,0x22,0x3d,0xf0}; static const unsigned char handler[]={0x30,0xb1,3,0x3b,0x33,0x30,0xb1,0x13,0,0x30,0}; static const uint32_t evectors[3]={0x1040,0x1080,0x10c0}; - unsigned char data[256]={0};esk_xtensa_windows w;esk_xtensa_memory bus={data,code_read,NULL,NULL,NULL}; + unsigned char data[256]={0};esk_xtensa_windows w;esk_xtensa_memory bus={data,code_read,NULL,NULL,NULL,NULL}; esk_xtensa_fault fault;unsigned event,steps=0,entries=0; memcpy(data,program,sizeof program);memcpy(data+64,handler,sizeof handler); esk_xtensa_windows_init(&w,0x1000,NULL);esk_xtensa_exception_configure(&w,evectors); diff --git a/tests/unit/test_xtensa_windows.c b/tests/unit/test_xtensa_windows.c index 9947c84e5..efe1a7a0e 100644 --- a/tests/unit/test_xtensa_windows.c +++ b/tests/unit/test_xtensa_windows.c @@ -132,7 +132,7 @@ static es_status mem_write(void *ctx,uint32_t address,unsigned bytes,uint32_t va static void exception_memory(void) { esk_xtensa_windows w,before; unsigned event; - test_memory m={0x89abcdef,0,0,0}; esk_xtensa_memory bus={&m,mem_read,mem_write,NULL,NULL}; + test_memory m={0x89abcdef,0,0,0}; esk_xtensa_memory bus={&m,mem_read,mem_write,NULL,NULL,NULL}; esk_xtensa_windows_init(&w,0x40000000,vectors); w.cpu.a[3]=192; before=w; CHECK_EQ(execute_bus(&w,&bus,0x090320,3,&event),ES_EUNSUPPORTED); /* L32E only in EXCM */ CHECK_EQ(m.reads,0); CHECK_EQ(memcmp(&w,&before,sizeof w),0); diff --git a/tests/vendor/README.md b/tests/vendor/README.md index bd1f7d840..3f33726c8 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -206,3 +206,9 @@ PDF and an independent array/time oracle. `run_spi_c.py --program-workers 128 reads, 32 programmed workers, 32 replays, 139,520 instructions, 432 observed BUSY reads. The external-time schedule is artificial, not processor timing. See [Giantec scope](../../docs/engineering/esp32-giantec-profile.md). + +`xtensa_integer/run_atomctl.py` uses actual ESP32 assembler/compiler tools for +3,072 register comparisons and 128 C local-SRAM conditional-store calls/2,816 +instructions through the native bus. It covers all 64 ATOMCTL values on both +cores; internal cache and complete MMU/boot remain unqualified. See the +[atomic-control contract](../../docs/engineering/esp32-atomic-control.md). diff --git a/tests/vendor/xtensa_integer/evidence/boot-with-atomctl-2026-11-05.json b/tests/vendor/xtensa_integer/evidence/boot-with-atomctl-2026-11-05.json new file mode 100644 index 000000000..39820460a --- /dev/null +++ b/tests/vendor/xtensa_integer/evidence/boot-with-atomctl-2026-11-05.json @@ -0,0 +1,47 @@ +{ + "boot_verified": false, + "outcome": "blocked", + "core": "PRO only", + "rom_sha256": "9115bbcd03da9f54d3baca5ad94d588a43da54ed332347137ba3dc9010df258b", + "flash_sha256": "52fcca3c31ed835d45e22826d42dd7a7db0c867171fa330f9c56b0a81df7bee7", + "reset_vector": "0x40000400", + "prid_fixture": "0x0000cdcd", + "prid_reference": "ESP-IDF v4.4.7 xtensa_context.h documents PRO=0xcdcd, APP=0xabab; not a hardware readout", + "rom_tail_policy": "zero-fill absent DROM tail to match QEMU region setup, not silicon contents", + "flash_profile": "immutable SPI1 fixture, not a physical flash identity", + "efuse_fixture_sha256": "2d8192ea7bdf3510bc117b4ea4b833a94b2ff2ceff252ce1610c297ed633d5bb", + "efuse_completion_fixture": "one external completion opportunity every 64 instruction attempts; not physical timing", + "status": -8, + "event": 0, + "pc": "0x400004c9", + "opcode": "0x506340", + "address": "0x400004c9", + "fault_kind": 1, + "attempts": 452, + "efuse_completions": 3, + "retired_before_stop": 451, + "trace_tail": [ + "0x40000488", + "0x4000048b", + "0x4000048e", + "0x40000491", + "0x40000494", + "0x40000497", + "0x40000499", + "0x4000049b", + "0x4000049e", + "0x400004c3", + "0x400004c6", + "0x400004c9" + ], + "next_blocker": "WITLB instruction at PC 0x400004c9; instruction-memory attribute programming unsupported; full native SDK boot unverified", + "fixture_note": "Synthetic unprogrammed eFuse input and explicit external completion schedule, not owner fuse contents or physical timing", + "sources_sha256": { + "core/mcu/xtensa/windows.c": "7e2eb328a7513966aec4293a0f3039779cb3847c0dd893ee6f66f084ce8e8a25", + "core/mcu/xtensa/windows.h": "4be5a4fe8e3d5362fcaf353ab3583a581022203f09cf851a3e5e24dec145d20f", + "core/mcu/xtensa/execute.h": "0bbbfd7d67db5e4bd687e2fca73d70101ff9f2891804e0d9c5df10af9c31aa59", + "core/mcu/esp32/fabric.c": "d0108eb686c05dcf9f2f4e0b9e21532b86d2c0310c371292cf49dc904605fddd", + "core/mcu/esp32/machine.c": "f2990dc402269ba829002a745ac468be6cf764c3bc3d30685cef2252a7bbe964", + "tests/vendor/xtensa_integer/probe_rom_boot.py": "324a648b6a086c7bddd6b0e37c221972667f50a55abaaac92e055de539beb623" + } +} diff --git a/tests/vendor/xtensa_integer/run_atomctl.py b/tests/vendor/xtensa_integer/run_atomctl.py new file mode 100644 index 000000000..01c6a1e3f --- /dev/null +++ b/tests/vendor/xtensa_integer/run_atomctl.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""GNU-assembled ATOMCTL register oracle and real ESP32 C local-SRAM RCW. + +ISA RI-2021.8 table 190 / section 4.3.13 supplies six bits and reset 0x28. +No private-cache internal operation, complete MMU or SDK boot qualification. +""" +import argparse +import ctypes as C +from pathlib import Path +import subprocess +import tempfile +from run_execute import ROOT, Memory, Fault +from run_windows_c import Windows +from run_machine_c import Allocator + + +def main(): + ap=argparse.ArgumentParser(description=__doc__) + for name in ['assembler','compiler','objcopy','linker','nm']:ap.add_argument('--'+name,required=True) + ap.add_argument('--cc',default='cc');args=ap.parse_args() + specs=[(form,reg) for form in ['rsr','wsr','xsr'] for reg in range(16)] + with tempfile.TemporaryDirectory(prefix='embsim-atomctl-') as tmp: + out=Path(tmp);src=out/'ops.S';obj=out/'ops.o';binary=out/'ops.bin' + src.write_text('.text\n.begin no-transform\n'+'\n'.join(f'{form} a{reg},ATOMCTL' for form,reg in specs)+'\n.end no-transform\n',encoding='utf-8') + subprocess.run([args.assembler,str(src),'-o',str(obj)],check=True) + subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(obj),str(binary)],check=True) + ops=binary.read_bytes();assert len(ops)==len(specs)*3 + sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/util/alloc.c'];lib=out/'atomctl.so' + subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) + api=C.CDLL(str(lib));pointer=C.c_void_p;wp=C.POINTER(Windows) + def bind(name,result,*params):f=getattr(api,name);f.restype=result;f.argtypes=list(params);return f + wi=bind('esk_xtensa_windows_init',None,wp,C.c_uint32,C.POINTER(C.c_uint32)) + enable=bind('esk_xtensa_atomctl_configure',C.c_int,wp) + execute=bind('esk_xtensa_window_execute',C.c_int,wp,pointer,pointer,C.c_size_t,C.POINTER(C.c_size_t),C.POINTER(C.c_uint),C.POINTER(Fault)) + comparisons=0 + for index,(form,reg) in enumerate(specs): + for value in range(64): + w=Windows();wi(C.byref(w),0x40080000,None);assert enable(C.byref(w))==0 and w.atomctl==0x28 + old=63-value;w.atomctl=old;w.cpu.a[reg]=value;used=C.c_size_t();event=C.c_uint();fault=Fault() + code=C.create_string_buffer(ops[index*3:index*3+3]) + assert execute(C.byref(w),None,code,3,C.byref(used),C.byref(event),C.byref(fault))==0 + assert used.value==3 and event.value==0 and w.cpu.pc==0x40080003 + assert w.atomctl==(value if form!='rsr' else old) + assert w.cpu.a[reg]==(value if form=='wsr' else old) + comparisons+=1 + source=out/'inspect.c';obj=out/'inspect.o';elf=out/'inspect.elf';binary=out/'inspect.bin' + source.write_text('''unsigned inspect(volatile unsigned *out,unsigned control) { + unsigned old,readback,observed=out[1]+1,expected=out[1]; + __asm__ volatile("rsr.atomctl %0" : "=a"(old)); + __asm__ volatile("wsr.atomctl %0; rsync" :: "a"(control) : "memory"); + __asm__ volatile("rsr.atomctl %0" : "=a"(readback)); + __asm__ volatile("wsr.scompare1 %1; s32c1i %0,%2,0" : "+a"(observed) : "a"(expected),"a"(out+1) : "memory"); + out[0]=readback;out[2]=observed; + __asm__ volatile("wsr.atomctl %0; rsync" :: "a"(old) : "memory"); + return out[1]; +} +''',encoding='utf-8') + subprocess.run([args.compiler,'-B'+str(Path(args.compiler).parent)+'/','-O1','-ffreestanding','-fno-builtin','-mtext-section-literals','-c',str(source),'-o',str(obj)],check=True) + subprocess.run([args.linker,'--no-relax','-Ttext=0x40080000','--entry=inspect',str(obj),'-o',str(elf)],check=True) + subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(elf),str(binary)],check=True) + symbols={f[2]:int(f[0],16) for line in subprocess.check_output([args.nm,str(elf)],text=True).splitlines() if len(f:=line.split())==3} + ai=bind('esk_alloc_init',None,C.POINTER(Allocator),pointer);create=bind('esk_esp32_machine_create',C.c_int,C.POINTER(Allocator),C.POINTER(pointer));destroy=bind('esk_esp32_machine_destroy',None,pointer) + fabric=bind('esk_esp32_machine_fabric',pointer,pointer);load=bind('esk_esp32_fabric_load',C.c_int,pointer,C.c_uint,C.c_uint32,pointer,C.c_size_t) + bus_get=bind('esk_esp32_machine_bus',Memory,pointer,C.c_uint);configure=bind('esk_esp32_machine_configure',C.c_int,pointer,C.c_uint,C.c_uint32) + core=bind('esk_esp32_machine_core',wp,pointer,C.c_uint);run=bind('esk_esp32_machine_run_core',C.c_int,pointer,C.c_uint,C.c_int) + step=bind('esk_esp32_machine_step',C.c_int,pointer,C.c_uint,C.POINTER(C.c_uint),C.POINTER(Fault)) + allocator=Allocator();ai(C.byref(allocator),None);m=pointer();assert create(C.byref(allocator),C.byref(m))==0 + calls=0;instructions=0;value=C.c_uint32();event=C.c_uint();fault=Fault() + try: + ram=fabric(m);code=binary.read_bytes();assert load(ram,0,0x40080000,C.c_char_p(code),len(code))==0 + bus=bus_get(m,0);assert bus.write(bus.ctx,0x3ff00030,4,1)==0 + for cpu in range(2): + for control in range(64): + assert configure(m,cpu,symbols['inspect'])==0 + w=core(m,cpu);assert enable(w)==0 + w.contents.ps|=1<<16 + w.contents.cpu.a[1]=0x3ffd0000+cpu*4096;w.contents.cpu.a[4]=0x7ffffff0 + w.contents.cpu.a[6]=0x3ffb9000;w.contents.cpu.a[7]=control + initial=0xffffffff if control==0 else control*73+cpu + assert bus.write(bus.ctx,0x3ffb9004,4,initial)==0 and run(m,cpu,1)==0 + for turn in range(200): + if w.contents.cpu.pc==0x7ffffff0:break + assert step(m,cpu,C.byref(event),C.byref(fault))==0 and event.value==0,(hex(fault.pc),hex(fault.opcode),event.value) + instructions+=1 + else:raise AssertionError('C acceptance exceeded step budget') + for offset,wanted in [(0,control),(4,(initial+1)&0xffffffff),(8,initial)]: + assert bus.read(bus.ctx,0x3ffb9000+offset,4,C.byref(value))==0 and value.value==wanted + assert w.contents.atomctl==0x28 and w.contents.cpu.a[6]==(initial+1)&0xffffffff + calls+=1 + finally:destroy(m) + assert allocator.live_bytes==0 + print(f'{comparisons} independent GNU ATOMCTL register comparisons; {calls} actual ESP32 C calls, {instructions} retired instructions, native local-SRAM atomic bus') + + +if __name__=='__main__':main() diff --git a/tests/vendor/xtensa_integer/run_execute.py b/tests/vendor/xtensa_integer/run_execute.py index af905cae9..c640fce87 100644 --- a/tests/vendor/xtensa_integer/run_execute.py +++ b/tests/vendor/xtensa_integer/run_execute.py @@ -32,8 +32,11 @@ class Fault(C.Structure): CAS = C.CFUNCTYPE(C.c_int, C.c_void_p, C.c_uint32, C.c_uint32, C.c_uint32, C.POINTER(C.c_uint32)) +AtomicType = C.CFUNCTYPE(C.c_int, C.c_void_p, C.c_uint32, C.POINTER(C.c_uint)) + + class Memory(C.Structure): - _fields_ = [('ctx', C.c_void_p), ('read', Read), ('write', Write), ('compare_exchange', CAS), ('fetch', Read)] + _fields_ = [('ctx', C.c_void_p), ('read', Read), ('write', Write), ('compare_exchange', CAS), ('fetch', Read), ('atomic_type', AtomicType)] def main(): diff --git a/tests/vendor/xtensa_integer/run_windows_c.py b/tests/vendor/xtensa_integer/run_windows_c.py index 044891596..5459b1529 100644 --- a/tests/vendor/xtensa_integer/run_windows_c.py +++ b/tests/vendor/xtensa_integer/run_windows_c.py @@ -22,7 +22,7 @@ class Windows(C.Structure): _fields_=[('cpu',State),('ar',C.c_uint32*64),('ps',C.c_uint32),('base',C.c_uint32), ('start',C.c_uint32),('epc1',C.c_uint32),('lbeg',C.c_uint32),('lend',C.c_uint32), ('lcount',C.c_uint32),('sar',C.c_uint32),('exccause',C.c_uint32),('excvaddr',C.c_uint32), - ('depc',C.c_uint32),('scompare1',C.c_uint32),('exceptions_enabled',C.c_uint32),('exception_vectors',C.c_uint32*3),('irq',Interrupts),('vectors',C.c_uint32*6),('vecbase',C.c_uint32),('vecbase_enabled',C.c_uint32),('threadptr',C.c_uint32),('misc',C.c_uint32*4),('prid',C.c_uint32),('prid_enabled',C.c_uint32)] + ('depc',C.c_uint32),('scompare1',C.c_uint32),('exceptions_enabled',C.c_uint32),('exception_vectors',C.c_uint32*3),('irq',Interrupts),('vectors',C.c_uint32*6),('vecbase',C.c_uint32),('vecbase_enabled',C.c_uint32),('threadptr',C.c_uint32),('misc',C.c_uint32*4),('prid',C.c_uint32),('prid_enabled',C.c_uint32),('atomctl',C.c_uint32),('atomctl_enabled',C.c_uint32)] NESTED=''' From 8ee4e001c23aa72dbafedbb0757ad6b9ea50b7a6 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 16:42:38 +0000 Subject: [PATCH 30/57] Keep atomic comparison fixtures unsigned for MSVC --- tests/unit/test_xtensa_atomic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_xtensa_atomic.c b/tests/unit/test_xtensa_atomic.c index f01714530..f10c64251 100644 --- a/tests/unit/test_xtensa_atomic.c +++ b/tests/unit/test_xtensa_atomic.c @@ -116,7 +116,7 @@ static void atomctl_memory_policy(void) w.cpu.pc=0x40080000;w.ps=0;w.cpu.a[3]=256;w.cpu.a[2]=42;w.scompare1=9;w.atomctl=control;before=w; if(mode==1) { CHECK_EQ(execute(&w,&bus,0x00e322,&event,&fault),ES_OK);CHECK_EQ(event,0); - CHECK_EQ(b.calls,1);CHECK_EQ(w.cpu.a[2],matched ? 9 : 8);CHECK_EQ(b.value,matched ? 42 : 8); + CHECK_EQ(b.calls,1);CHECK_EQ(w.cpu.a[2],matched ? 9u : 8u);CHECK_EQ(b.value,matched ? 42u : 8u); } else if(!mode) { CHECK_EQ(execute(&w,&bus,0x00e322,&event,&fault),ES_OK);CHECK_EQ(event,7);CHECK_EQ(b.calls,0); CHECK_EQ(w.exccause,3);CHECK_EQ(w.excvaddr,256);CHECK_EQ(w.epc1,before.cpu.pc);CHECK_EQ(w.cpu.a[2],42); From dab33437c8e7edf432184e4825c465c8f8f4b272 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 16:44:38 +0000 Subject: [PATCH 31/57] Implement original ESP32 region protection and TLB maintenance --- CHANGELOG.md | 14 ++ core/mcu/xtensa/execute.c | 29 ++-- core/mcu/xtensa/execute.h | 7 +- core/mcu/xtensa/interrupts.c | 6 +- core/mcu/xtensa/windows.c | 85 ++++++++++-- core/mcu/xtensa/windows.h | 12 ++ docs/STATUS.md | 7 +- docs/engineering/esp32-atomic-control.md | 6 +- docs/engineering/esp32-boot-verification.md | 11 +- docs/engineering/esp32-dual-core.md | 3 +- docs/engineering/esp32-region-protection.md | 130 ++++++++++++++++++ tests/unit/test_xtensa_regions.c | 128 +++++++++++++++++ tests/vendor/README.md | 6 + .../boot-with-regions-2026-11-05.json | 92 +++++++++++++ tests/vendor/xtensa_integer/probe_rom_boot.py | 7 +- tests/vendor/xtensa_integer/run_regions.py | 119 ++++++++++++++++ tests/vendor/xtensa_integer/run_windows_c.py | 2 +- 17 files changed, 631 insertions(+), 33 deletions(-) create mode 100644 docs/engineering/esp32-region-protection.md create mode 100644 tests/unit/test_xtensa_regions.c create mode 100644 tests/vendor/xtensa_integer/evidence/boot-with-regions-2026-11-05.json create mode 100644 tests/vendor/xtensa_integer/run_regions.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f71ee901..05d37071f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Original ESP32 region protection + +- Added an explicit eight-region instruction/data protection profile, all ten + TLB maintenance operations, reset/readback and ISYNC/DSYNC access-table commit. + No page-table translation or SoC-cache completeness is claimed. +- Enforced fetch/load/store rights before bus access, with causes 20/28/29, + kernel/user/double dispatch, and atomic store-protection priority. Shared pure + load/store address decoding preserves the flat-memory execution contract. +- Qualified the one-ring configuration and local-only isolate access boundary. + 17,068 native assertions, 266,240 GNU comparisons and 32 actual ESP32 C calls + pass. Native ROM now reaches 545 instructions and unsupported RSR.MEMCTL at + PC 0x400004f2; full SDK boot remains unverified. + + ### Original ESP32 CPU atomic control - Added opt-in privileged RSR/WSR/XSR.ATOMCTL, six-bit state/reset and explicit diff --git a/core/mcu/xtensa/execute.c b/core/mcu/xtensa/execute.c index d36288921..f43acd7b3 100644 --- a/core/mcu/xtensa/execute.c +++ b/core/mcu/xtensa/execute.c @@ -85,16 +85,8 @@ static es_status execute_inner(esk_xtensa_state *state, const esk_xtensa_memory } else if (op == 12 && t >= 8) { int taken = t < 12 ? next.a[s] == 0 : next.a[s] != 0; if (taken) { next.pc = pc + 4 + ((t & 3u) << 4 | r); flow = 0; } - } else if (op == 8 || op == 9) { - bytes = 4; addr = next.a[s] + (uint32_t)r * 4; store = op == 9; - } else if (op == 2 && (r <= 2 || r == 4 || r == 5 || r == 6 || r == 9 || r == 11 || r == 15)) { - unsigned scale = (r == 0 || r == 4) ? 0u : (r == 1 || r == 5 || r == 9) ? 1u : 2u; - bytes = 1u << scale; addr = next.a[s] + ((word >> 16) << scale); - /* Synchronous completed callbacks already satisfy L32AI acquisition - * and S32RI release ordering. No queued/cache bus is supported here. */ - store = (r >= 4 && r <= 6) || r == 15; signed16 = r == 9; - } else if (op == 1) { /* L32R without extended LITBASE option */ - bytes = 4; addr = ((pc + 3) & UINT32_C(0xfffffffc)) + ((word >> 8 | UINT32_C(0xffff0000)) << 2); + } else if (esk_xtensa_memory_reference(state,word,width,&addr,&bytes,&store,&signed16)) { + /* Shared pure descriptor also supplies windowed permission preflight. */ } else return fail(fault, ESK_XT_UNSUPPORTED, pc, pc, word); if (bytes) { if (addr & (bytes - 1u)) return fail(fault, ESK_XT_ALIGN, pc, addr, word); @@ -119,6 +111,23 @@ es_status esk_xtensa_execute(esk_xtensa_state *state, const esk_xtensa_memory *m { return execute_inner(state,memory,code,available,consumed,fault,NULL); } +int esk_xtensa_memory_reference(const esk_xtensa_state *state,uint32_t word,unsigned width, + uint32_t *address,unsigned *bytes,int *store,int *signed16) +{ + unsigned op=word&15u,s=(word>>8)&15u,r=(word>>12)&15u;int sign=0; + if(!state || !address || !bytes || !store) return 0; + if(width==2 && (op==8 || op==9)) { + *bytes=4;*address=state->a[s]+r*4u;*store=op==9; + } else if(width==3 && op==2 && (r<=2 || r==4 || r==5 || r==6 || r==9 || r==11 || r==15)) { + unsigned scale=(r==0 || r==4) ? 0u : (r==1 || r==5 || r==9) ? 1u : 2u; + *bytes=1u<a[s]+((word>>16)<=4 && r<=6) || r==15;sign=r==9; + } else if(width==3 && op==1) { + *bytes=4;*address=((state->pc+3u)&UINT32_C(0xfffffffc))+((word>>8|UINT32_C(0xffff0000))<<2);*store=0; + } else return 0; + if(signed16) *signed16=sign; + return 1; +} es_status esk_xtensa_execute_flow(esk_xtensa_state *state, const esk_xtensa_memory *memory, const unsigned char *code, size_t available, size_t *consumed, esk_xtensa_fault *fault, unsigned *sequential) diff --git a/core/mcu/xtensa/execute.h b/core/mcu/xtensa/execute.h index 4ce68c4a4..ecc9f410d 100644 --- a/core/mcu/xtensa/execute.h +++ b/core/mcu/xtensa/execute.h @@ -34,8 +34,13 @@ typedef struct esk_xtensa_memory { es_status (*atomic_type)(void *ctx,uint32_t address,unsigned *type); } esk_xtensa_memory; enum { ESK_XT_OK, ESK_XT_UNSUPPORTED, ESK_XT_TRUNCATED, ESK_XT_FETCH, - ESK_XT_ALIGN, ESK_XT_LOAD, ESK_XT_STORE, ESK_XT_DIV_ZERO, ESK_XT_ATOMIC }; + ESK_XT_ALIGN, ESK_XT_LOAD, ESK_XT_STORE, ESK_XT_DIV_ZERO, ESK_XT_ATOMIC, ESK_XT_PROTECTION }; typedef struct esk_xtensa_fault { unsigned kind; uint32_t pc, address, opcode; } esk_xtensa_fault; +/* Pure descriptor for supported base load/store encodings. word/width must + * already be decoded; returns 1 for a memory reference, 0 otherwise. + * Shared by execution and windowed region protection; no bus access. */ +int esk_xtensa_memory_reference(const esk_xtensa_state *state,uint32_t word,unsigned width, + uint32_t *address,unsigned *bytes,int *store,int *signed16); /* Execute supplied instruction bytes; no state commit on failure. */ es_status esk_xtensa_execute(esk_xtensa_state *state, const esk_xtensa_memory *memory, const unsigned char *code, size_t available, diff --git a/core/mcu/xtensa/interrupts.c b/core/mcu/xtensa/interrupts.c index 56d290e29..3a6cab803 100644 --- a/core/mcu/xtensa/interrupts.c +++ b/core/mcu/xtensa/interrupts.c @@ -66,7 +66,7 @@ es_status esk_xtensa_irq_poll(struct esk_xtensa_windows *w,unsigned *event) es_status esk_xtensa_irq_return(struct esk_xtensa_windows *w,unsigned level) { if (!w || !w->irq.enabled || level<2 || level>7) return ES_EUNSUPPORTED; - w->ps=w->irq.eps[level]&ESK_XT_PS_MASK;w->cpu.pc=w->irq.epc[level];return ES_OK; + w->ps=w->irq.eps[level]&ESK_XT_PS_MASK&~(w->region_enabled ? UINT32_C(0xc0) : 0u);w->cpu.pc=w->irq.epc[level];return ES_OK; } int esk_xtensa_irq_special(unsigned reg) { @@ -76,7 +76,7 @@ es_status esk_xtensa_irq_read(const struct esk_xtensa_windows *w,unsigned reg,ui { if (!w || !w->irq.enabled || !value) return ES_EUNSUPPORTED; if (reg>=178 && reg<=183) *value=w->irq.epc[reg-176]; - else if (reg>=194 && reg<=199) *value=w->irq.eps[reg-192]; + else if (reg>=194 && reg<=199) *value=w->irq.eps[reg-192]&~(w->region_enabled ? UINT32_C(0xc0) : 0u); else if (reg>=209 && reg<=215) *value=w->irq.excsave[reg-208]; else if (reg==226) *value=esk_xtensa_irq_pending(w); else if (reg==228) *value=w->irq.intenable; @@ -89,7 +89,7 @@ es_status esk_xtensa_irq_write(struct esk_xtensa_windows *w,unsigned reg,uint32_ { if (!w || !w->irq.enabled) return ES_EUNSUPPORTED; if (reg>=178 && reg<=183) w->irq.epc[reg-176]=value; - else if (reg>=194 && reg<=199) w->irq.eps[reg-192]=value; + else if (reg>=194 && reg<=199) w->irq.eps[reg-192]=value&~(w->region_enabled ? UINT32_C(0xc0) : 0u); else if (reg>=209 && reg<=215) w->irq.excsave[reg-208]=value; else if (reg==226) w->irq.latched|=value&SOFTWARE; else if (reg==227) w->irq.latched&=~(value&(SOFTWARE|EDGE)); diff --git a/core/mcu/xtensa/windows.c b/core/mcu/xtensa/windows.c index dd2b14e75..995050e2c 100644 --- a/core/mcu/xtensa/windows.c +++ b/core/mcu/xtensa/windows.c @@ -8,7 +8,7 @@ enum execution_kind { XT_BASE, XT_CALL, XT_ENTRY, XT_RETW, XT_WINDOW_RETURN, XT_ROTW, XT_SPECIAL, XT_RSYNC, XT_LOOP, XT_EXCEPTION_MEMORY, XT_SAR_SETUP=11, XT_SAR_SHIFT, XT_FENCE, XT_RSIL, XT_SYSCALL, - XT_EXCEPTION_RETURN, XT_ILLEGAL, XT_RFI, XT_WAITI, XT_ATOMIC, XT_DIVIDE, XT_THREADPTR, XT_MOVSP + XT_EXCEPTION_RETURN, XT_ILLEGAL, XT_RFI, XT_WAITI, XT_ATOMIC, XT_DIVIDE, XT_THREADPTR, XT_MOVSP, XT_REGION }; static uint32_t bit(unsigned n) { return UINT32_C(1) << (n & 15u); } @@ -31,6 +31,32 @@ void esk_xtensa_windows_init(esk_xtensa_windows *w, uint32_t pc, const uint32_t memset(w,0,sizeof *w); w->cpu.pc=pc; w->ps=ESK_XT_PS_WOE; w->start=1; if (vectors) memcpy(w->vectors,vectors,sizeof w->vectors); } +es_status esk_xtensa_region_configure(esk_xtensa_windows *w) +{ + unsigned bank,entry;if(!w) return ES_EINVAL; + w->region_enabled=1;w->ps&=~UINT32_C(0xc0); + for(bank=0;bank<2;bank++) for(entry=0;entry<8;entry++) w->region[bank][entry]=w->region_active[bank][entry]=2; + return ES_OK; +} +/* Original ESP32 core-isa.h local port windows, not SoC backing allocation. + * Attribute rights apply even to local memory. No cache lookup is synthesized. */ +static int region_local(uint32_t address) +{ + return (address>=0x3f400000 && address<0x3fc00000) || + (address>=0x3ff00000 && address<0x40000000) || + (address>=0x40000000 && address<0x40c00000); +} +static es_status region_access(const esk_xtensa_windows *w,uint32_t address,unsigned bytes,unsigned access,unsigned *cause) +{ + static const unsigned rights[16]={3,7,7,4,7,7,7,0,0,0,0,0,0,0,3,0}; + unsigned i;*cause=0;if(!w->region_enabled) return ES_OK; + for(i=0;iregion_active[access==2 ? 0 : 1][a>>29]; + if(!(rights[attr&15u]&(1u<1) return ES_EINVAL; (void)esk_xtensa_atomctl_configure(w); + if(w->region_enabled) (void)esk_xtensa_region_configure(w); save(w);w->base&=15u;w->start&=65535u; w->ps=0x1f;w->lcount=0;w->cpu.pc=vector_input ? 0x40000400u : 0x50000000u; w->exceptions_enabled=1;w->irq.enabled=1;w->irq.waiting=0; @@ -98,7 +125,7 @@ static uint32_t special_read(const esk_xtensa_windows *w,unsigned reg) switch (reg) { case 0:return w->lbeg; case 1:return w->lend; case 2:return w->lcount; case 3:return w->sar; case 99:return w->atomctl; case 12:return w->scompare1; case 72:return w->base; case 73:return w->start; case 177:return w->epc1; - case 192:return w->depc; case 230:return w->ps&ESK_XT_PS_MASK; case 235:return w->prid; case 231:return w->vecbase; case 232:return w->exccause; default:return w->excvaddr; + case 192:return w->depc; case 230:return w->ps&ESK_XT_PS_MASK&~(w->region_enabled ? UINT32_C(0xc0) : 0u); case 235:return w->prid; case 231:return w->vecbase; case 232:return w->exccause; default:return w->excvaddr; } } static void special_write(esk_xtensa_windows *w,unsigned reg,uint32_t value) @@ -113,7 +140,7 @@ static void special_write(esk_xtensa_windows *w,unsigned reg,uint32_t value) case 72:w->base=value&15u;load(w);break; case 73:w->start=value&65535u;break; case 177:w->epc1=value;break; case 192:w->depc=value;break; - case 230:w->ps=value&ESK_XT_PS_MASK;break; case 231:(void)esk_xtensa_vecbase_configure(w,value);break; case 232:w->exccause=value;break; default:w->excvaddr=value;break; + case 230:w->ps=value&ESK_XT_PS_MASK&~(w->region_enabled ? UINT32_C(0xc0) : 0u);break; case 231:(void)esk_xtensa_vecbase_configure(w,value);break; case 232:w->exccause=value;break; default:w->excvaddr=value;break; } } /* Register references are checked before any access. Zero/compare branches do @@ -208,7 +235,12 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo else if ((word&UINT32_C(0xff000f))==0x910000 && !s) { kind=XT_SAR_SHIFT; n=1; mask=bit(r)|bit(t); } else if ((word&UINT32_C(0xff000f))==0xb10000 && !s) { kind=XT_SAR_SHIFT; n=2; mask=bit(r)|bit(t); } else if ((word&UINT32_C(0xff000f))==0x810000) { kind=XT_SAR_SHIFT; n=3; mask=bit(r)|bit(s)|bit(t); } - else if (word==0x0020c0 || word==0x002000 || word==0x002020) kind=XT_FENCE; + else if ((word&UINT32_C(0xff000f))==0x500000 && next.region_enabled && + (r==3 || r==4 || r==5 || r==6 || r==7 || r==11 || r==12 || r==13 || r==14 || r==15) && + ((r!=4 && r!=12) || !t)) { + kind=XT_REGION;mask=bit(s);if(r!=4 && r!=12) mask|=bit(t); + } + else if (word==0x0020c0 || word==0x002000 || word==0x002020 || word==0x002030) kind=XT_FENCE; else if ((word&UINT32_C(0xfff00f))==0x006000) { kind=XT_RSIL; mask=bit(t); } else if (word==0x005000) kind=XT_SYSCALL; else if (word==0 || word==0xf06d) kind=XT_ILLEGAL; @@ -220,14 +252,38 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo else if (word==0x002010) kind=XT_RSYNC; /* RSYNC; no asynchronous pipeline here */ else if (references(code,width,word,&mask)!=ES_OK) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); if ((kind==XT_WINDOW_RETURN || kind==XT_ROTW || - (kind==XT_SPECIAL && ((word>>8)&255u)>=64) || kind==XT_RSIL || kind==XT_EXCEPTION_RETURN || kind==XT_RFI || kind==XT_WAITI) && - !(next.ps&ESK_XT_PS_EXCM) && ((next.ps>>6)&3u)) { + (kind==XT_SPECIAL && ((word>>8)&255u)>=64) || kind==XT_REGION || kind==XT_RSIL || kind==XT_EXCEPTION_RETURN || kind==XT_RFI || kind==XT_WAITI) && + !next.region_enabled && !(next.ps&ESK_XT_PS_EXCM) && ((next.ps>>6)&3u)) { if (!next.exceptions_enabled) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); st=esk_xtensa_raise_exception(&next,8,0,0,event); if (st==ES_OK) *w=next; return st; } e=overflow(&next,mask); if (e) { *w=next; *event=e; return ES_OK; } - if (kind==XT_MOVSP) { + if(next.region_enabled) { + uint32_t address=0;unsigned bytes=0,cause=0;int write=0,has=0; + if(kind==XT_ATOMIC) { address=next.cpu.a[s]+((word>>16)<<2);bytes=4;write=1;has=1; } + else if(kind==XT_EXCEPTION_MEMORY) { address=next.cpu.a[s]+((((word>>12)&15u)-16u)<<2);bytes=4;write=(word&UINT32_C(0x400000))!=0;has=1; } + else if(kind==XT_BASE) has=esk_xtensa_memory_reference(&next.cpu,word,width,&address,&bytes,&write,NULL); + if(has && !(address&(bytes-1u))) { + st=region_access(&next,address,bytes,write ? 1u : 0u,&cause); + if(st!=ES_OK) { + if(cause && next.exceptions_enabled) { st=esk_xtensa_raise_exception(&next,cause,address,1,event);if(st==ES_OK) *w=next;return st; } + return reject(fault,cause ? ESK_XT_PROTECTION : ESK_XT_UNSUPPORTED,pc,address,word); + } + } + } + if(kind==XT_REGION) { + unsigned bank=r>=8 ? 1u : 0u,entry=next.cpu.a[s]>>29,action=r&7u; + uint32_t value=next.cpu.a[t]; + if(action==6) { + if(value&UINT32_C(0xff0)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + next.region[bank][entry]=value&15u; + } else if(action==3) next.cpu.a[t]=0; + else if(action==7) next.cpu.a[t]=(entry<<29)|next.region[bank][entry]; + else if(action==5) next.cpu.a[t]=(entry<<29)|1u; + /* Invalidate cannot remove hardwired identity-mapped entries. */ + next.cpu.pc=pc+3; + } else if (kind==XT_MOVSP) { if (!(next.start&(bit(next.base-1u)|bit(next.base-2u)|bit(next.base-3u)))) { if(!next.exceptions_enabled) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); st=esk_xtensa_raise_exception(&next,5,0,0,event);if(st==ES_OK) *w=next;return st; @@ -307,7 +363,13 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo else if (n==2) joined=(uint64_t)next.cpu.a[t] | ((next.cpu.a[t]>>31) ? UINT64_C(0xffffffff00000000) : 0); else joined=(uint64_t)next.cpu.a[s]<<32 | next.cpu.a[t]; next.cpu.a[r]=(uint32_t)(joined>>next.sar); next.cpu.pc=pc+3; - } else if (kind==XT_FENCE) next.cpu.pc=pc+3; /* fully serialized functional bus, no pipeline/cache claim */ + } else if (kind==XT_FENCE) { + if(next.region_enabled && (word==0x002000 || word==0x002030)) { + unsigned bank=word==0x002030 ? 1u : 0u; + memcpy(next.region_active[bank],next.region[bank],sizeof next.region[bank]); + } + next.cpu.pc=pc+3; + } /* fully serialized functional bus, no pipeline/cache claim */ else if (kind==XT_RSIL) { next.cpu.a[t]=next.ps; next.ps=(next.ps&~15u)|s; next.cpu.pc=pc+3; } else if (kind==XT_SYSCALL || kind==XT_ILLEGAL) { if (!next.exceptions_enabled) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); @@ -370,7 +432,12 @@ es_status esk_xtensa_window_step(esk_xtensa_windows *w, const esk_xtensa_memory if (w->irq.waiting) { *event=18; return ES_OK; } } for (i=0;icpu.pc+i; + uint32_t address=w->cpu.pc+i;unsigned cause=0; + es_status permission=region_access(w,address,1,2,&cause); + if(permission!=ES_OK) { + if(cause && w->exceptions_enabled) return esk_xtensa_raise_exception(w,cause,address,1,event); + return reject(fault,cause ? ESK_XT_PROTECTION : ESK_XT_UNSUPPORTED,w->cpu.pc,address,0); + } if (!memory || (!memory->fetch && !memory->read) || (memory->fetch ? memory->fetch : memory->read)(memory->ctx,address,1,&value)!=ES_OK) return reject(fault,ESK_XT_FETCH,w->cpu.pc,address,0); diff --git a/core/mcu/xtensa/windows.h b/core/mcu/xtensa/windows.h index e94876978..93b86d137 100644 --- a/core/mcu/xtensa/windows.h +++ b/core/mcu/xtensa/windows.h @@ -29,6 +29,7 @@ typedef struct esk_xtensa_windows { uint32_t vecbase,vecbase_enabled; uint32_t threadptr,misc[4],prid,prid_enabled; uint32_t atomctl,atomctl_enabled; + uint32_t region_enabled,region[2][8],region_active[2][8]; } esk_xtensa_windows; /* event 0: retired; 1..6: window transition; 7/8/9: kernel/user/double exception; * 11..17: accepted interrupt level 1..7; 18: suspended WAITI (step only). @@ -43,6 +44,17 @@ typedef struct esk_xtensa_windows { * configuration. This supplies a constant board/SoC input, not a guessed ESP32 * silicon ID or a live pin-waveform model; it survives supported CPU reset. */ es_status esk_xtensa_prid_configure(esk_xtensa_windows *state,uint32_t value); +/* Original-ESP32 Region Protection Option: eight identity-mapped 512MiB + * regions per ITLB/DTLB, attributes reset to 2. Explicit opt-in; CPU reset + * resets tables only while enabled. One hardware ring, no Xtensa I/D cache. + * WxTLB/RxTLB/PxTLB and non-removing IxTLB; fetch/data permissions raise + * causes 20/28/29 before bus access. Unsupported instructions remain failures. + * Readback changes at WxTLB; ISYNC/DSYNC commit instruction/data access tables. + * Pre-barrier behavior uses the old table as a deterministic fixture policy. No pipeline/cycle, page-table/translation, device + * cache or external cache-isolate qualification. Isolate data on nonlocal + * addresses fails. Reserved unused low operand bits fail unchanged. + * execute consumes supplied bytes; step checks actual instruction fetches. */ +es_status esk_xtensa_region_configure(esk_xtensa_windows *state); /* Opt-in conditional-store ATOMCTL SR99, six bits, reset 0x28. * RSR/WSR/XSR privileged; reserved upper-bit writes fail unchanged. * S32C1I requires bus atomic_type: LOCAL ignores fields; 0 raises cause 3, diff --git a/docs/STATUS.md b/docs/STATUS.md index 970df7f21..6aa6846ed 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -10,10 +10,13 @@ does not mean the described behaviour exists. **Current implementation update:** an [explicit eFuse read/reload profile](engineering/esp32-efuse-read-profile.md) now supports None-coded input images, read protection and completion routing. With a supplied synthetic unprogrammed image, real ROM execution advances from -seven to 451 retired instructions, then blocks on WITLB at PC 0x400004c9. +seven to 545 retired instructions, then blocks on RSR.MEMCTL at PC 0x400004f2. +[Region protection](engineering/esp32-region-protection.md) now implements fixed +ITLB/DTLB maintenance, synchronized permissions and causes 20/28/29. [ATOMCTL](engineering/esp32-atomic-control.md) now has six-bit privileged register access, reset, memory-type selection, local/RCW atomics and cause-3 dispatch. -Internal cache operations and full instruction/data attributes remain absent. +Internal cache operations, complete memory-system/loop-buffer behavior and full +SDK boot remain absent. Read completion is an explicit external event; no physical duration is inferred. The earlier synchronous profile took the ROM failure/reset path. The owner's actual fuse image is not inferred. A separate diff --git a/docs/engineering/esp32-atomic-control.md b/docs/engineering/esp32-atomic-control.md index 182c3798c..7ea4e82f1 100644 --- a/docs/engineering/esp32-atomic-control.md +++ b/docs/engineering/esp32-atomic-control.md @@ -73,7 +73,11 @@ through the same core-specific bus view used for accesses. including zero/reserved modes. The bus is native, with no Python memory callbacks. Host ABI function setup is explicit; this is not application boot. -The real-ROM probe now retires **451 instructions**, including WSR.ATOMCTL=0x15, +The subsequent [region profile](esp32-region-protection.md) advances the probe +to RSR.MEMCTL after 545 instructions. The historical atomic-only result below +retains its earlier source scope. + +The real-ROM probe at this milestone retires **451 instructions**, including WSR.ATOMCTL=0x15, then stops at unsupported **WITLB**, opcode 0x506340, PC 0x400004c9. The [recorded probe](../../tests/vendor/xtensa_integer/evidence/boot-with-atomctl-2026-11-05.json) uses the same external ROM, actual SDK image, synthetic None-coded eFuse image, diff --git a/docs/engineering/esp32-boot-verification.md b/docs/engineering/esp32-boot-verification.md index 5b3b5b88f..70f691a46 100644 --- a/docs/engineering/esp32-boot-verification.md +++ b/docs/engineering/esp32-boot-verification.md @@ -3,7 +3,7 @@ **Result on 2026-11-05: the owner's board reports ESP32-D0WD-V3 revision 3.1, JEDEC C4 60 16 and 4 MB flash. Giantec's GT25Q32A-U datasheet matches that ID; exact package/ordering suffix remains unconfirmed. The pinned SDK example boots -in Espressif QEMU. Native EmbSIM stops at unsupported WITLB after 451 retired +in Espressif QEMU. Native EmbSIM stops at unsupported RSR.MEMCTL after 545 retired instructions with the new explicit synthetic eFuse image; without that profile, the baseline stops at an eFuse read after seven.** Neither result establishes successful native boot. @@ -114,7 +114,12 @@ configuration or printed SDK capacity must not substitute for hardware identity. ## Native EmbSIM result: blocked -**Latest follow-up:** [ATOMCTL](esp32-atomic-control.md) now passes the former +**Latest follow-up:** [Region protection](esp32-region-protection.md) now completes +both instruction/data setup loops and barriers. The explicit-input probe reaches +545 retirements and unsupported RSR.MEMCTL at PC 0x400004f2. Full SDK boot remains +unverified. + +**Prior atomic-control result:** [ATOMCTL](esp32-atomic-control.md) now passes the former CPU-register blocker. The same explicit-input probe reaches 451 retirements and unsupported WITLB at PC 0x400004c9. Native SDK boot remains blocked. @@ -141,7 +146,7 @@ fault load failure (kind 5), status -1 Pinned SDK `soc/esp32/include/soc/efuse_reg.h` identifies that address as `EFUSE_BLK0_RDATA0_REG`. This baseline deliberately has no eFuse profile attached. With the new explicit image attached, the first observed blocker -moved first to WSR.ATOMCTL and now to WITLB, as recorded above. The earlier synchronous profile took +moved first to WSR.ATOMCTL, then WITLB, and now RSR.MEMCTL, as recorded above. The earlier synchronous profile took the ROM failure path and requested a software reset through RTC options; it did not pass the eFuse handshake. RTC/strapping/reset state, further eFuse behavior, dedicated flash diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index a3e79de6e..78581e590 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -31,7 +31,8 @@ that first access when a caller supplies all 31 uncoded words. A synthetic unprogrammed fixture with explicit read-completion events advances ROM to 440 retired instructions in the prior profile. With [atomic-operation control](esp32-atomic-control.md), it now reaches 451 instructions -and unsupported WITLB at PC 0x400004c9. It is not the owner's fuse dump and does +in the prior profile. With [fixed-region protection](esp32-region-protection.md), +it reaches 545 instructions and unsupported RSR.MEMCTL at PC 0x400004f2. It is not the owner's fuse dump and does not establish full boot. The [Giantec storage subset](esp32-giantec-profile.md) adds independently checked timing/WEL behavior for the matching JEDEC family; complete chip behavior remains unqualified. diff --git a/docs/engineering/esp32-region-protection.md b/docs/engineering/esp32-region-protection.md new file mode 100644 index 000000000..8a7ab9ec9 --- /dev/null +++ b/docs/engineering/esp32-region-protection.md @@ -0,0 +1,130 @@ +# Original ESP32 instruction and data region protection + +**Implemented: the opt-in fixed-region protection profile, its ten TLB maintenance +instructions, synchronized access attributes and fetch/load/store protection. +Page-table translation, a complete memory subsystem, MEMCTL/loop-buffer behavior +and native SDK boot remain unfinished.** + +## Real configuration and operations + +The pinned original-ESP32 `core-isa.h` selects `HAVE_MIMIC_CACHEATTR=1`, no region +translation or page-table MMU, one hardware ring and zero Xtensa instruction/data +cache bytes. This is distinct from the SoC's external-flash MMU/cache behind its +local instruction/data ports. Region protection must not be described as that +complete SoC cache or as a page-table MMU. + +Xtensa ISA RI-2021.8 section 5.3 defines separate instruction/data tables, each +containing eight hardwired identity mappings of 512 MiB. Upper address bits 31:29 +select the entry. All attributes reset to 2, bypass. `esk_xtensa_region_configure` +enables this profile explicitly; enabled CPU reset restores both tables to 2. +Legacy ABI fixtures remain unchanged until opt-in. One hardware ring means +PS.RING does not create lower privilege: its bits read/write as zero in PS/EPS +access and return, and privileged operations remain available. PS.UM still +selects the existing user exception path. + +| Instruction | Observable result | +|---|---| +| WITLB / WDTLB | Write the selected entry's low four attribute bits | +| RITLB0 / RDTLB0 | Return zero; VPN is already the fixed index | +| RITLB1 / RDTLB1 | Return the region base OR its attribute | +| PITLB / PDTLB | Return the region base OR 1; every region always hits | +| IITLB / IDTLB | Leave mappings intact; hardwired entries cannot be removed | + +All operand registers, including aliases between source/destination, follow +normal read-before-write and window-reference checks. Address operand low bits +are ignored. Write-data bits 31:12 are ignored; unused bits 11:4 must be zero, +otherwise the operation fails unchanged rather than inventing undefined effects. +Unused read-result bits are deterministically zero. + +WxTLB readback changes on retirement. ISYNC commits the instruction access table; +DSYNC commits the data access table. Until the required barrier, the old access +table is the declared deterministic fixture policy. The architecture leaves that +interval undefined; software must not rely on this chosen old-table behavior. +MEMW/RSYNC/ESYNC do not substitute for these TLB barriers. There is no physical +pipeline, prefetch-latency or synchronization-delay qualification. + +## Permissions and bus effects + +The profile applies the ISA attribute rights before routing to local memory or +an external bus: + +| Attribute | Read | Write | Execute | +|---|---|---|---| +| 0, 14 | yes | yes | no | +| 1, 2, 4, 5, 6 | yes | yes | yes | +| 3 | no | no | yes | +| 7..13, 15 | no | no | no | + +A denied fetch raises cause 20; a denied load, 28; a denied store, 29. EXCVADDR +records the denied address and the configured kernel/user/double path saves the +faulting instruction address. The rejected data access does not call the bus. +Fetch permission is checked before each fetched byte; an instruction crossing a +region boundary can fetch preceding permitted bytes before a later denial. +Without exception configuration, rejection returns a protection fault unchanged. +Alignment and unsupported-instruction limitations remain those of the existing +engine; this is not qualification of every CPU memory fault or priority. + +Protection covers the implemented byte/halfword/word loads/stores, density forms, +signed load, literal load, acquire/release, window-exception accesses and S32C1I. +The pure address descriptor is shared with ordinary execution, avoiding a second +set of normal load/store address calculations. S32C1I tests store capability only +and can raise cause 29, never cause 28, even though it reads during exchange. +Protection runs before atomic type classification or compare/exchange. + +Local-port windows come from the pinned core configuration, not invented backing +memory. For local accesses the ISA consults only rights. There is no Xtensa cache +in this configuration. Cache-isolate attribute 14 data operations outside local +ports fail explicitly because an external isolate implementation is unqualified. +Device/SoC cache state and unavailable memory remain the provider's responsibility; +setting region rights does not allocate RAM or make unknown MMIO work. + +`window_execute` consumes supplied instruction bytes and checks their data +accesses. `window_step` additionally performs actual instruction fetch protection. +CPU interrupts are still polled before fetch; exception transitions do not retire +the faulting instruction. No instructions are silently treated as NOPs except the +architecturally non-removing invalidate behavior for these fixed tables. + +## Evidence + +- `test_xtensa_regions`: **17,068 assertions** across both tables, every region + and all attributes; maintenance readback, synchronization/reset, ignored upper + and rejected low bits, one-ring PS behavior, the permission matrix, zero data + bus effects on denial, atomic store priority and all supported memory forms + through kernel/user/double exceptions. +- `run_regions.py`: **266,240 independent GNU-assembled instruction/register/ + alias comparisons**. It hash-checks actual original-ESP32 core configuration. +- Actual ESP32 GCC compiles original C that reads/writes/probes/invalidates data + entries, executes DSYNC and restores permissions before storing results. + **32 calls, 480 retired instructions**, all 16 attributes on both cores, pass + through the native bus without Python memory callbacks. Host ABI function + setup is explicit; this is not SDK boot or a physical timing comparison. +- Existing flat-memory, windows, exceptions, atomic, timer, TLS, reset, mapped + flash, SPI programming and eFuse compiler/vendor fixtures also rerun. + +The external real-ROM probe completes both instruction/data maintenance loops +and ISYNC/DSYNC, retires **545 instructions**, then stops at unsupported +**RSR.MEMCTL**, opcode 0x036120, PC 0x400004f2. The +[hash-recorded result](../../tests/vendor/xtensa_integer/evidence/boot-with-regions-2026-11-05.json) +uses the same external ROM/SDK image, explicit synthetic eFuse image, PRO PRID and +artificial completion schedule as the [boot investigation](esp32-boot-verification.md). +Both readback and synchronized access tables are `[15,1,1,2,2,2,2,2]`, +recorded separately for instruction/data. No PC shortcut or fallback MMIO is introduced. This does not verify complete +ROM-to-application boot; MEMCTL and subsequent requirements remain open. + +## References and reproduction + +Xtensa ISA Summary RI-2021.8, section 5.3, table 95 and instruction descriptions; +external PDF SHA-256 +`474c4aaf2d429ff79c48127e738c4110cecd8072133169ad6d00a050edf244b7`. +Original ESP32 `core-isa.h` SHA-256 +`60d20f76c7730b24041b9f55b16e0062f30bf85c7661c7ed215f050fedcdb344`. +No vendor implementation or ROM is included in runtime source. + +```sh +python3 tests/vendor/xtensa_integer/run_regions.py \ + --assembler /path/to/xtensa-esp32-elf-as \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld --nm /path/to/xtensa-esp32-elf-nm \ + --core-isa /path/to/xtensa/config/core-isa.h +``` diff --git a/tests/unit/test_xtensa_regions.c b/tests/unit/test_xtensa_regions.c new file mode 100644 index 000000000..e5be29204 --- /dev/null +++ b/tests/unit/test_xtensa_regions.c @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/xtensa/windows.h" +typedef struct fixture { unsigned reads,writes,cas;uint32_t value; } fixture; +static es_status read_word(void *ctx,uint32_t address,unsigned bytes,uint32_t *value) +{ + fixture *f=(fixture *)ctx;(void)address;(void)bytes;f->reads++;*value=f->value;return ES_OK; +} +static es_status write_word(void *ctx,uint32_t address,unsigned bytes,uint32_t value) +{ + fixture *f=(fixture *)ctx;(void)address;(void)bytes;f->writes++;f->value=value;return ES_OK; +} +static es_status fetch_byte(void *ctx,uint32_t address,unsigned bytes,uint32_t *value) +{ + fixture *f=(fixture *)ctx;(void)bytes;f->reads++;*value=(address&1u) ? 0xf0u : 0x3du;return ES_OK; +} +static es_status exchange(void *ctx,uint32_t address,uint32_t expected,uint32_t desired,uint32_t *observed) +{ + fixture *f=(fixture *)ctx;(void)address;f->cas++;*observed=f->value;if(f->value==expected) f->value=desired;return ES_OK; +} +static es_status execute(esk_xtensa_windows *w,const esk_xtensa_memory *bus,uint32_t word,unsigned *event) +{ + unsigned char code[3];size_t used=99;es_status st;esk_xtensa_fault fault; + code[0]=(unsigned char)(word&255u);code[1]=(unsigned char)((word>>8)&255u);code[2]=(unsigned char)((word>>16)&255u); + st=esk_xtensa_window_execute(w,bus,code,3,&used,event,&fault); + CHECK_EQ(used,st==ES_OK && !*event ? ((word&15u)<8 ? 3u : 2u) : 0u);return st; +} +static void initialise(esk_xtensa_windows *w) +{ + esk_xtensa_windows_init(w,0x40081000,NULL);CHECK_EQ(esk_xtensa_lx6_reset(w,1),ES_OK); + CHECK_EQ(esk_xtensa_region_configure(w),ES_OK);w->cpu.pc=0x40081000;w->ps=0; +} +static void instructions_and_barriers(void) +{ + unsigned bank,entry,attr,event,i;esk_xtensa_windows w,before; + CHECK_EQ(esk_xtensa_region_configure(NULL),ES_EINVAL); + esk_xtensa_windows_init(&w,0x40081000,NULL);before=w; + CHECK_EQ(execute(&w,NULL,0x506320,&event),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&w,&before,sizeof w),0); + for(bank=0;bank<2;bank++) for(entry=0;entry<8;entry++) for(attr=0;attr<16;attr++) { + initialise(&w);w.cpu.a[2]=UINT32_C(0xa5a50000)|attr;w.cpu.a[3]=(entry<<29)|0x1fffffffu; + CHECK_EQ(execute(&w,NULL,0x506320u+(bank<<15),&event),ES_OK); + CHECK_EQ(w.region[bank][entry],attr);CHECK_EQ(w.region_active[bank][entry],2); + CHECK_EQ(execute(&w,NULL,0x503340u+(bank<<15),&event),ES_OK);CHECK_EQ(w.cpu.a[4],0); + CHECK_EQ(execute(&w,NULL,0x507340u+(bank<<15),&event),ES_OK);CHECK_EQ(w.cpu.a[4],(entry<<29)|attr); + CHECK_EQ(execute(&w,NULL,0x505340u+(bank<<15),&event),ES_OK);CHECK_EQ(w.cpu.a[4],(entry<<29)|1u); + CHECK_EQ(execute(&w,NULL,0x504300u+(bank<<15),&event),ES_OK);CHECK_EQ(w.region[bank][entry],attr); + CHECK_EQ(execute(&w,NULL,0x0020c0,&event),ES_OK);CHECK_EQ(w.region_active[bank][entry],2); + CHECK_EQ(execute(&w,NULL,bank ? 0x002030u : 0x002000u,&event),ES_OK); + CHECK_EQ(w.region_active[bank][entry],attr); + for(i=0;i<8;i++) CHECK_EQ(w.region_active[1-bank][i],2); + CHECK_EQ(esk_xtensa_lx6_reset(&w,1),ES_OK); + for(i=0;i<8;i++) { CHECK_EQ(w.region[bank][i],2);CHECK_EQ(w.region_active[bank][i],2); } + } + initialise(&w);w.cpu.a[3]=0; + for(i=4;i<12;i++) { + w.cpu.a[2]=UINT32_C(1)<=12 ? 0x3ffb0ffcu : 0x3ffb1000u; + initialise(&w);w.cpu.a[3]=0x3ffb1000;w.ps=(mode==2 || i>=12) ? ESK_XT_PS_EXCM : mode==1 ? 32u : 0u; + w.region[1][address>>29]=15;CHECK_EQ(execute(&w,&bus,0x002030,&event),ES_OK); + CHECK_EQ(execute(&w,&bus,forms[i],&event),ES_OK); + CHECK_EQ(event,(mode==2 || i>=12) ? 9u : mode==1 ? 8u : 7u); + CHECK_EQ(w.exccause,stores[i] ? 29u : 28u);CHECK_EQ(w.excvaddr,address); + CHECK_EQ(f.reads,0);CHECK_EQ(f.writes,0); + } + initialise(&w);w.cpu.a[3]=0x3ffb1000;w.region[1][1]=15; + CHECK_EQ(execute(&w,&bus,0x002030,&event),ES_OK);w.exceptions_enabled=0;before=w; + CHECK_EQ(execute(&w,&bus,0x002322,&event),ES_EINVAL);CHECK_EQ(memcmp(&w,&before,sizeof w),0);CHECK_EQ(f.reads,0); +} +TEST_MAIN_BEGIN + (void)t_str_eq;RUN(instructions_and_barriers);RUN(permission_matrix);RUN(atomic_priority_and_fetch_commit);RUN(memory_forms_and_exception_modes); +TEST_MAIN_END diff --git a/tests/vendor/README.md b/tests/vendor/README.md index 3f33726c8..054604211 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -212,3 +212,9 @@ See [Giantec scope](../../docs/engineering/esp32-giantec-profile.md). instructions through the native bus. It covers all 64 ATOMCTL values on both cores; internal cache and complete MMU/boot remain unqualified. See the [atomic-control contract](../../docs/engineering/esp32-atomic-control.md). + +`xtensa_integer/run_regions.py` uses pinned original ESP32 core configuration +and actual assembler/compiler tools: 266,240 TLB instruction/register/alias +comparisons, plus 32 C maintenance calls/480 instructions on the native bus. +See the [region-protection contract](../../docs/engineering/esp32-region-protection.md) +for synchronization, access rights, one-ring behavior and omitted memory features. diff --git a/tests/vendor/xtensa_integer/evidence/boot-with-regions-2026-11-05.json b/tests/vendor/xtensa_integer/evidence/boot-with-regions-2026-11-05.json new file mode 100644 index 000000000..ddf552cf2 --- /dev/null +++ b/tests/vendor/xtensa_integer/evidence/boot-with-regions-2026-11-05.json @@ -0,0 +1,92 @@ +{ + "boot_verified": false, + "outcome": "blocked", + "core": "PRO only", + "rom_sha256": "9115bbcd03da9f54d3baca5ad94d588a43da54ed332347137ba3dc9010df258b", + "flash_sha256": "52fcca3c31ed835d45e22826d42dd7a7db0c867171fa330f9c56b0a81df7bee7", + "reset_vector": "0x40000400", + "region_profile": "original ESP32 eight ITLB/DTLB identity regions, reset attribute 2, one ring, no Xtensa cache", + "prid_fixture": "0x0000cdcd", + "prid_reference": "ESP-IDF v4.4.7 xtensa_context.h documents PRO=0xcdcd, APP=0xabab; not a hardware readout", + "rom_tail_policy": "zero-fill absent DROM tail to match QEMU region setup, not silicon contents", + "flash_profile": "immutable SPI1 fixture, not a physical flash identity", + "efuse_fixture_sha256": "2d8192ea7bdf3510bc117b4ea4b833a94b2ff2ceff252ce1610c297ed633d5bb", + "efuse_completion_fixture": "one external completion opportunity every 64 instruction attempts; not physical timing", + "status": -8, + "event": 0, + "pc": "0x400004f2", + "opcode": "0x36120", + "address": "0x400004f2", + "fault_kind": 1, + "attempts": 546, + "efuse_completions": 3, + "retired_before_stop": 545, + "trace_tail": [ + "0x400004e0", + "0x400004e3", + "0x400004e6", + "0x400004e9", + "0x400004dd", + "0x400004e0", + "0x400004e3", + "0x400004e6", + "0x400004e9", + "0x400004ec", + "0x400004ef", + "0x400004f2" + ], + "next_blocker": "RSR.MEMCTL, special register 97, PC 0x400004f2; native SDK boot remains unverified", + "fixture_note": "Explicit synthetic fuse image and external completion schedule, not owner fuse contents or physical timing", + "sources_sha256": { + "core/mcu/xtensa/windows.c": "af6f3f2aff650dce1a4606edc1e9b4c180cce13786fa3a470fa91be19baec6a1", + "core/mcu/xtensa/windows.h": "844269c5fb18557686b3493298b1e14d5400b1e1c3fb55f4216fbf649fa671cd", + "core/mcu/xtensa/execute.c": "c69879eb07ab393416c396a6789c5f638d77810016a2932c58e51b71e2ba7fa7", + "core/mcu/xtensa/execute.h": "41c63a66f1c38b2a589bf78748a7588a24e6a3bff391e8709152817c12de7698", + "core/mcu/xtensa/interrupts.c": "429607ba6873226a3be8d19ec420595ff2238964e4896365fc8165e669f0b031", + "tests/vendor/xtensa_integer/probe_rom_boot.py": "7237a7fb707622eb87cf8d084f5838d057464d6649bb31504a9ea848f7a9c0de" + }, + "region_readback": [ + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ], + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ] + ], + "region_access_attributes": [ + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ], + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ] + ] +} diff --git a/tests/vendor/xtensa_integer/probe_rom_boot.py b/tests/vendor/xtensa_integer/probe_rom_boot.py index 5810211bb..addf33574 100644 --- a/tests/vendor/xtensa_integer/probe_rom_boot.py +++ b/tests/vendor/xtensa_integer/probe_rom_boot.py @@ -57,6 +57,7 @@ def bind(name,result,*params): complete=bind('esk_esp32_fabric_efuse_read_complete',C.c_int,pointer) spi=bind('esk_esp32_fabric_spi1_configure',C.c_int,pointer,C.c_int) core=bind('esk_esp32_machine_core',C.POINTER(Windows),pointer,C.c_uint) + region=bind('esk_xtensa_region_configure',C.c_int,C.POINTER(Windows)) reset=bind('esk_xtensa_lx6_reset',C.c_int,C.POINTER(Windows),C.c_uint) prid=bind('esk_xtensa_prid_configure',C.c_int,C.POINTER(Windows),C.c_uint32) run=bind('esk_esp32_machine_run_core',C.c_int,pointer,C.c_uint,C.c_int) @@ -66,7 +67,7 @@ def bind(name,result,*params): assert create(C.byref(allocator),C.byref(machine))==0 report={'boot_verified':False,'outcome':'step_budget_inconclusive','core':'PRO only', 'rom_sha256':ROM_SHA256,'flash_sha256':hashlib.sha256(image).hexdigest(), - 'reset_vector':'0x40000400','prid_fixture':f'0x{args.prid:08x}', + 'reset_vector':'0x40000400','region_profile':'original ESP32 eight ITLB/DTLB identity regions, reset attribute 2, one ring, no Xtensa cache','prid_fixture':f'0x{args.prid:08x}', 'prid_reference':'ESP-IDF v4.4.7 xtensa_context.h documents PRO=0xcdcd, APP=0xabab; not a hardware readout', 'rom_tail_policy':'zero-fill absent DROM tail to match QEMU region setup, not silicon contents', 'flash_profile':'immutable SPI1 fixture, not a physical flash identity'} @@ -83,7 +84,7 @@ def bind(name,result,*params): data=rom[0x60000:]+bytes(0x70000-len(rom));assert len(data)==65536 assert install(ram,1,C.c_char_p(data),len(data))==0 assert flash(ram,C.c_char_p(image),len(image))==0 and spi(ram,1)==0 - w=core(machine,0);assert reset(w,1)==0 and prid(w,args.prid)==0 and run(machine,0,1)==0 + w=core(machine,0);assert reset(w,1)==0 and region(w)==0 and prid(w,args.prid)==0 and run(machine,0,1)==0 event=C.c_uint();fault=Fault();trace=deque(maxlen=12) completions=0 report['efuse_completion_fixture']=f'one external completion opportunity every {args.efuse_completion_period} instruction attempts; not physical timing' @@ -97,6 +98,8 @@ def bind(name,result,*params): pc=hex(fault.pc if status else pc),opcode=hex(fault.opcode),address=hex(fault.address), fault_kind=fault.kind,attempts=attempt+1) break + report['region_readback']=[list(bank) for bank in w.contents.region] + report['region_access_attributes']=[list(bank) for bank in w.contents.region_active] report.update(efuse_completions=completions,retired_before_stop=retired(machine,0),trace_tail=list(trace)) finally:destroy(machine) assert allocator.live_bytes==0 diff --git a/tests/vendor/xtensa_integer/run_regions.py b/tests/vendor/xtensa_integer/run_regions.py new file mode 100644 index 000000000..5eb1bc19b --- /dev/null +++ b/tests/vendor/xtensa_integer/run_regions.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""GNU Region Protection Option oracle and ESP32 C TLB maintenance. + +Pinned original ESP32 core configuration; no full MMU/device-cache/SDK boot claim. +""" +import argparse +import ctypes as C +from pathlib import Path +import subprocess +import tempfile +from run_execute import ROOT, Memory, Fault +from run_windows_c import Windows +from run_machine_c import Allocator +from run_matrix import read_header +import re + + +def main(): + ap=argparse.ArgumentParser(description=__doc__) + for name in ['assembler','compiler','objcopy','linker','nm']:ap.add_argument('--'+name,required=True) + ap.add_argument('--core-isa',type=Path,required=True) + ap.add_argument('--cc',default='cc');args=ap.parse_args() + config=read_header(args.core_isa,'60d20f76c7730b24041b9f55b16e0062f30bf85c7661c7ed215f050fedcdb344') + facts=dict(re.findall(r'^#define\s+(XCHAL_\w+)\s+(\S+)',config,re.M)) + for key,wanted in [('HAVE_MIMIC_CACHEATTR',1),('HAVE_PTP_MMU',0),('HAVE_XLT_CACHEATTR',0),('MMU_RINGS',1),('ICACHE_SIZE',0),('DCACHE_SIZE',0)]:assert int(facts['XCHAL_'+key],0)==wanted + specs=[(form,t,src) for form in ['witlb','wdtlb','ritlb0','ritlb1','rdtlb0','rdtlb1','pitlb','pdtlb'] for t in range(16) for src in range(16)] + specs += [(form,None,src) for form in ['iitlb','idtlb'] for src in range(16)] + with tempfile.TemporaryDirectory(prefix='embsim-regions-') as tmp: + out=Path(tmp);src=out/'ops.S';obj=out/'ops.o';binary=out/'ops.bin' + src.write_text('.text\n.begin no-transform\n'+'\n'.join((f'{form} a{src}' if t is None else f'{form} a{t},a{src}') for form,t,src in specs)+'\n.end no-transform\n',encoding='utf-8') + subprocess.run([args.assembler,str(src),'-o',str(obj)],check=True) + subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(obj),str(binary)],check=True) + ops=binary.read_bytes();assert len(ops)==len(specs)*3 + sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/util/alloc.c'];lib=out/'regions.so' + subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) + api=C.CDLL(str(lib));pointer=C.c_void_p;wp=C.POINTER(Windows) + def bind(name,result,*params):f=getattr(api,name);f.restype=result;f.argtypes=list(params);return f + wi=bind('esk_xtensa_windows_init',None,wp,C.c_uint32,C.POINTER(C.c_uint32)) + enable=bind('esk_xtensa_region_configure',C.c_int,wp) + execute=bind('esk_xtensa_window_execute',C.c_int,wp,pointer,pointer,C.c_size_t,C.POINTER(C.c_size_t),C.POINTER(C.c_uint),C.POINTER(Fault)) + comparisons=0 + for index,(form,t,src) in enumerate(specs): + bank=int(form in ['wdtlb','rdtlb0','rdtlb1','pdtlb','idtlb']) + for entry in range(8): + for attr in range(16): + w=Windows();wi(C.byref(w),0x40080000,None);assert enable(C.byref(w))==0 + registers=[0xdead0000+i for i in range(16)];registers[src]=(entry<<29)|0x1fffffff + if form in ['witlb','wdtlb']:registers[t]=0xa5a50000|attr + for i,v in enumerate(registers):w.cpu.a[i]=v + tables=[[j for j in range(8)],[15-j for j in range(8)]] + for k in range(2): + for j in range(8):w.region[k][j]=tables[k][j] + selected=registers[src]>>29 + if form in ['witlb','wdtlb']:tables[bank][selected]=registers[t]&15 + elif form in ['ritlb0','rdtlb0']:registers[t]=0 + elif form in ['ritlb1','rdtlb1']:registers[t]=(selected<<29)|tables[bank][selected] + elif form in ['pitlb','pdtlb']:registers[t]=(selected<<29)|1 + used=C.c_size_t();event=C.c_uint();fault=Fault() + code=C.create_string_buffer(ops[index*3:index*3+3]) + assert execute(C.byref(w),None,code,3,C.byref(used),C.byref(event),C.byref(fault))==0,(form,t,src) + assert used.value==3 and event.value==0 and w.cpu.pc==0x40080003 + assert list(w.cpu.a)==registers + assert [list(row) for row in w.region]==tables + assert [list(row) for row in w.region_active]==[[2]*8,[2]*8] + comparisons+=1 + source=out/'inspect.c';obj=out/'inspect.o';elf=out/'inspect.elf';binary=out/'inspect.bin' + source.write_text('''unsigned inspect(volatile unsigned *out,unsigned attr) { + unsigned old,readback,probe; + __asm__ volatile("rdtlb1 %0,%1" : "=a"(old) : "a"(out)); + __asm__ volatile("wdtlb %0,%1; dsync" :: "a"(attr),"a"(out) : "memory"); + __asm__ volatile("rdtlb1 %0,%1" : "=a"(readback) : "a"(out)); + __asm__ volatile("pdtlb %0,%1; idtlb %1" : "=&a"(probe) : "a"(out)); + __asm__ volatile("wdtlb %0,%1; dsync" :: "a"(old),"a"(out) : "memory"); + out[0]=readback;out[1]=probe; + return readback; +} +''',encoding='utf-8') + subprocess.run([args.compiler,'-B'+str(Path(args.compiler).parent)+'/','-O1','-ffreestanding','-fno-builtin','-mtext-section-literals','-c',str(source),'-o',str(obj)],check=True) + subprocess.run([args.linker,'--no-relax','-Ttext=0x40080000','--entry=inspect',str(obj),'-o',str(elf)],check=True) + subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(elf),str(binary)],check=True) + symbols={f[2]:int(f[0],16) for line in subprocess.check_output([args.nm,str(elf)],text=True).splitlines() if len(f:=line.split())==3} + ai=bind('esk_alloc_init',None,C.POINTER(Allocator),pointer);create=bind('esk_esp32_machine_create',C.c_int,C.POINTER(Allocator),C.POINTER(pointer));destroy=bind('esk_esp32_machine_destroy',None,pointer) + fabric=bind('esk_esp32_machine_fabric',pointer,pointer);load=bind('esk_esp32_fabric_load',C.c_int,pointer,C.c_uint,C.c_uint32,pointer,C.c_size_t) + bus_get=bind('esk_esp32_machine_bus',Memory,pointer,C.c_uint);configure=bind('esk_esp32_machine_configure',C.c_int,pointer,C.c_uint,C.c_uint32) + core=bind('esk_esp32_machine_core',wp,pointer,C.c_uint);run=bind('esk_esp32_machine_run_core',C.c_int,pointer,C.c_uint,C.c_int) + step=bind('esk_esp32_machine_step',C.c_int,pointer,C.c_uint,C.POINTER(C.c_uint),C.POINTER(Fault)) + allocator=Allocator();ai(C.byref(allocator),None);m=pointer();assert create(C.byref(allocator),C.byref(m))==0 + calls=0;instructions=0;value=C.c_uint32();event=C.c_uint();fault=Fault() + try: + ram=fabric(m);code=binary.read_bytes();assert load(ram,0,0x40080000,C.c_char_p(code),len(code))==0 + bus=bus_get(m,0);assert bus.write(bus.ctx,0x3ff00030,4,1)==0 + for cpu in range(2): + for control in range(16): + assert configure(m,cpu,symbols['inspect'])==0 + w=core(m,cpu);assert enable(w)==0 + w.contents.ps|=1<<16 + w.contents.cpu.a[1]=0x3ffd0000+cpu*4096;w.contents.cpu.a[4]=0x7ffffff0 + w.contents.cpu.a[6]=0x3ffb9000;w.contents.cpu.a[7]=control + initial=0 + assert bus.write(bus.ctx,0x3ffb9004,4,initial)==0 and run(m,cpu,1)==0 + for turn in range(200): + if w.contents.cpu.pc==0x7ffffff0:break + assert step(m,cpu,C.byref(event),C.byref(fault))==0 and event.value==0,(hex(fault.pc),hex(fault.opcode),event.value) + instructions+=1 + else:raise AssertionError('C acceptance exceeded step budget') + for offset,wanted in [(0,0x20000000|control),(4,0x20000001)]: + assert bus.read(bus.ctx,0x3ffb9000+offset,4,C.byref(value))==0 and value.value==wanted + assert list(w.contents.region_active[1])==[2]*8 and w.contents.cpu.a[6]==0x20000000|control + calls+=1 + finally:destroy(m) + assert allocator.live_bytes==0 + print(f'{comparisons} independent GNU region instruction/register/alias comparisons; {calls} actual ESP32 C calls, {instructions} retired instructions, native bus and restored data permissions') + + +if __name__=='__main__':main() diff --git a/tests/vendor/xtensa_integer/run_windows_c.py b/tests/vendor/xtensa_integer/run_windows_c.py index 5459b1529..a88a7c823 100644 --- a/tests/vendor/xtensa_integer/run_windows_c.py +++ b/tests/vendor/xtensa_integer/run_windows_c.py @@ -22,7 +22,7 @@ class Windows(C.Structure): _fields_=[('cpu',State),('ar',C.c_uint32*64),('ps',C.c_uint32),('base',C.c_uint32), ('start',C.c_uint32),('epc1',C.c_uint32),('lbeg',C.c_uint32),('lend',C.c_uint32), ('lcount',C.c_uint32),('sar',C.c_uint32),('exccause',C.c_uint32),('excvaddr',C.c_uint32), - ('depc',C.c_uint32),('scompare1',C.c_uint32),('exceptions_enabled',C.c_uint32),('exception_vectors',C.c_uint32*3),('irq',Interrupts),('vectors',C.c_uint32*6),('vecbase',C.c_uint32),('vecbase_enabled',C.c_uint32),('threadptr',C.c_uint32),('misc',C.c_uint32*4),('prid',C.c_uint32),('prid_enabled',C.c_uint32),('atomctl',C.c_uint32),('atomctl_enabled',C.c_uint32)] + ('depc',C.c_uint32),('scompare1',C.c_uint32),('exceptions_enabled',C.c_uint32),('exception_vectors',C.c_uint32*3),('irq',Interrupts),('vectors',C.c_uint32*6),('vecbase',C.c_uint32),('vecbase_enabled',C.c_uint32),('threadptr',C.c_uint32),('misc',C.c_uint32*4),('prid',C.c_uint32),('prid_enabled',C.c_uint32),('atomctl',C.c_uint32),('atomctl_enabled',C.c_uint32),('region_enabled',C.c_uint32),('region',(C.c_uint32*8)*2),('region_active',(C.c_uint32*8)*2)] NESTED=''' From 56c7ee52621effa9e29fe02441b586f278fd53db Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 17:50:22 +0000 Subject: [PATCH 32/57] Implement functional ESP32 loop buffering and cycle-capture tooling --- CHANGELOG.md | 17 +++ core/mcu/xtensa/windows.c | 73 ++++++++---- core/mcu/xtensa/windows.h | 11 ++ docs/STATUS.md | 11 +- docs/engineering/esp32-boot-verification.md | 11 +- docs/engineering/esp32-dual-core.md | 6 +- docs/engineering/esp32-instruction-timing.md | 65 ++++++++++ docs/engineering/esp32-memctl-loop-buffer.md | 95 +++++++++++++++ docs/engineering/esp32-region-protection.md | 6 +- tests/CMakeLists.txt | 2 + tests/unit/test_xtensa_memctl.c | 112 ++++++++++++++++++ tests/vendor/README.md | 10 ++ .../xtensa_integer/cycle_probe/CMakeLists.txt | 5 + .../xtensa_integer/cycle_probe/README.md | 72 +++++++++++ .../xtensa_integer/cycle_probe/analyze.py | 74 ++++++++++++ .../cycle_probe/main/CMakeLists.txt | 4 + .../cycle_probe/main/cycle_probe.c | 86 ++++++++++++++ .../cycle_probe/sdkconfig.defaults | 6 + .../cycle_probe/test_analyze.py | 44 +++++++ .../evidence/boot-with-memctl-2026-11-05.json | 92 ++++++++++++++ .../cycle-probe-build-2026-11-05.json | 41 +++++++ tests/vendor/xtensa_integer/probe_rom_boot.py | 5 +- tests/vendor/xtensa_integer/run_memctl.py | 98 +++++++++++++++ tests/vendor/xtensa_integer/run_windows_c.py | 2 +- 24 files changed, 916 insertions(+), 32 deletions(-) create mode 100644 docs/engineering/esp32-instruction-timing.md create mode 100644 docs/engineering/esp32-memctl-loop-buffer.md create mode 100644 tests/unit/test_xtensa_memctl.c create mode 100644 tests/vendor/xtensa_integer/cycle_probe/CMakeLists.txt create mode 100644 tests/vendor/xtensa_integer/cycle_probe/README.md create mode 100644 tests/vendor/xtensa_integer/cycle_probe/analyze.py create mode 100644 tests/vendor/xtensa_integer/cycle_probe/main/CMakeLists.txt create mode 100644 tests/vendor/xtensa_integer/cycle_probe/main/cycle_probe.c create mode 100644 tests/vendor/xtensa_integer/cycle_probe/sdkconfig.defaults create mode 100644 tests/vendor/xtensa_integer/cycle_probe/test_analyze.py create mode 100644 tests/vendor/xtensa_integer/evidence/boot-with-memctl-2026-11-05.json create mode 100644 tests/vendor/xtensa_integer/evidence/cycle-probe-build-2026-11-05.json create mode 100644 tests/vendor/xtensa_integer/run_memctl.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 05d37071f..b70605268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Original ESP32 MEMCTL and timing qualification tools + +- Added opt-in MEMCTL bit 0 with reset, privileged read/write/exchange and a + 256-byte functional loop-buffer policy, including disable/ISYNC/bounds/reset/ + exception invalidation, protected hits and failed-fetch/decode rollback. + Exact silicon fill/replacement, fetch geometry and instruction timing remain + unqualified. Legacy fixtures retain explicit opt-in. +- 268,225 native assertions, 96 GNU register comparisons and 20 compiled C + hardware-loop calls pass. Native ROM reaches 6,692 instructions, then blocks + on RTC reset-state read at 0x3ff48034, PC 0x400081df. SDK boot remains unverified. +- Added buildable ESP-IDF cycle-capture firmware and strict log analysis for + both cores, loop-buffer modes, nine IRAM kernels and 3,672 observations. + Actual ESP32 GCC build and a complete QEMU protocol run pass. No physical + board was programmed/measured, and no instruction latency model is inferred + from emulator values. Invalid/incomplete captures cannot claim qualification. + + ### Original ESP32 region protection - Added an explicit eight-region instruction/data protection profile, all ten diff --git a/core/mcu/xtensa/windows.c b/core/mcu/xtensa/windows.c index 995050e2c..fb4810d43 100644 --- a/core/mcu/xtensa/windows.c +++ b/core/mcu/xtensa/windows.c @@ -31,6 +31,15 @@ void esk_xtensa_windows_init(esk_xtensa_windows *w, uint32_t pc, const uint32_t memset(w,0,sizeof *w); w->cpu.pc=pc; w->ps=ESK_XT_PS_WOE; w->start=1; if (vectors) memcpy(w->vectors,vectors,sizeof w->vectors); } +static void loop_buffer_flush(esk_xtensa_windows *w) +{ + memset(w->loopbuf_valid,0,sizeof w->loopbuf_valid); +} +es_status esk_xtensa_memctl_configure(esk_xtensa_windows *w) +{ + if(!w) return ES_EINVAL; + w->memctl=1;w->memctl_enabled=1;w->loopbuf_base=w->loopbuf_end=0;loop_buffer_flush(w);return ES_OK; +} es_status esk_xtensa_region_configure(esk_xtensa_windows *w) { unsigned bank,entry;if(!w) return ES_EINVAL; @@ -82,6 +91,7 @@ es_status esk_xtensa_lx6_reset(esk_xtensa_windows *w,unsigned vector_input) if(!w || vector_input>1) return ES_EINVAL; (void)esk_xtensa_atomctl_configure(w); if(w->region_enabled) (void)esk_xtensa_region_configure(w); + if(w->memctl_enabled) (void)esk_xtensa_memctl_configure(w); save(w);w->base&=15u;w->start&=65535u; w->ps=0x1f;w->lcount=0;w->cpu.pc=vector_input ? 0x40000400u : 0x50000000u; w->exceptions_enabled=1;w->irq.enabled=1;w->irq.waiting=0; @@ -107,6 +117,7 @@ es_status esk_xtensa_raise_exception(esk_xtensa_windows *w, unsigned cause, if (!w->exceptions_enabled) return ES_EUNSUPPORTED; if (w->ps&ESK_XT_PS_EXCM) { w->depc=w->cpu.pc; index=2; } else { w->epc1=w->cpu.pc; index=(w->ps&32u) ? 1u : 0u; } + if(w->memctl_enabled) loop_buffer_flush(w); w->exccause=cause; if (has_address) w->excvaddr=address; w->ps|=ESK_XT_PS_EXCM; @@ -116,7 +127,7 @@ es_status esk_xtensa_raise_exception(esk_xtensa_windows *w, unsigned cause, } static int special_valid(const esk_xtensa_windows *w,unsigned reg) { - return (w->irq.enabled && esk_xtensa_irq_special(reg)) || (reg==99 && w->atomctl_enabled) || reg<=3 || reg==12 || reg==72 || reg==73 || reg==177 || reg==192 || reg==230 || (reg==231 && w->vecbase_enabled) || reg==232 || reg==238 || (reg>=244 && reg<=247) || (reg==235 && w->prid_enabled); + return (w->irq.enabled && esk_xtensa_irq_special(reg)) || (reg==97 && w->memctl_enabled) || (reg==99 && w->atomctl_enabled) || reg<=3 || reg==12 || reg==72 || reg==73 || reg==177 || reg==192 || reg==230 || (reg==231 && w->vecbase_enabled) || reg==232 || reg==238 || (reg>=244 && reg<=247) || (reg==235 && w->prid_enabled); } static uint32_t special_read(const esk_xtensa_windows *w,unsigned reg) { @@ -124,7 +135,7 @@ static uint32_t special_read(const esk_xtensa_windows *w,unsigned reg) if (esk_xtensa_irq_special(reg)) { uint32_t value=0; (void)esk_xtensa_irq_read(w,reg,&value); return value; } switch (reg) { case 0:return w->lbeg; case 1:return w->lend; case 2:return w->lcount; case 3:return w->sar; - case 99:return w->atomctl; case 12:return w->scompare1; case 72:return w->base; case 73:return w->start; case 177:return w->epc1; + case 97:return w->memctl; case 99:return w->atomctl; case 12:return w->scompare1; case 72:return w->base; case 73:return w->start; case 177:return w->epc1; case 192:return w->depc; case 230:return w->ps&ESK_XT_PS_MASK&~(w->region_enabled ? UINT32_C(0xc0) : 0u); case 235:return w->prid; case 231:return w->vecbase; case 232:return w->exccause; default:return w->excvaddr; } } @@ -133,8 +144,9 @@ static void special_write(esk_xtensa_windows *w,unsigned reg,uint32_t value) if(reg>=244 && reg<=247) { w->misc[reg-244]=value;return; } if (esk_xtensa_irq_special(reg)) { (void)esk_xtensa_irq_write(w,reg,value); return; } switch (reg) { - case 0:w->lbeg=value;break; case 1:w->lend=value;break; case 2:w->lcount=value;break; + case 0:w->lbeg=value;loop_buffer_flush(w);break; case 1:w->lend=value;loop_buffer_flush(w);break; case 2:w->lcount=value;break; case 3:w->sar=value&63u;break; + case 97:w->memctl=value;if(!value) loop_buffer_flush(w);break; case 99:w->atomctl=value;break; case 12:w->scompare1=value;break; case 72:w->base=value&15u;load(w);break; @@ -214,7 +226,7 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo else if ((word&UINT32_C(0xffff0f))==0x408000) kind=XT_ROTW; else if ((word&UINT32_C(0xff000f))==0x030000 || (word&UINT32_C(0xff000f))==0x130000 || (word&UINT32_C(0xff000f))==0x610000) { unsigned reg=(word>>8)&255u; - if (!special_valid(&next,reg) || (reg==227 && (word&UINT32_C(0xff000f))==0x030000) || ((word&UINT32_C(0xff000f))==0x610000 && reg!=3 && reg!=12 && reg!=230 && reg!=231 && reg!=99 && !(reg>=244 && reg<=247)) || (reg==235 && (word&UINT32_C(0xff000f))!=0x030000)) + if (!special_valid(&next,reg) || (reg==227 && (word&UINT32_C(0xff000f))==0x030000) || ((word&UINT32_C(0xff000f))==0x610000 && reg!=3 && reg!=12 && reg!=230 && reg!=231 && reg!=99 && reg!=97 && !(reg>=244 && reg<=247)) || (reg==235 && (word&UINT32_C(0xff000f))!=0x030000)) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); kind=XT_SPECIAL; mask=bit(t); } @@ -258,7 +270,7 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo st=esk_xtensa_raise_exception(&next,8,0,0,event); if (st==ES_OK) *w=next; return st; } e=overflow(&next,mask); - if (e) { *w=next; *event=e; return ES_OK; } + if (e) { if(next.memctl_enabled) loop_buffer_flush(&next);*w=next; *event=e; return ES_OK; } if(next.region_enabled) { uint32_t address=0;unsigned bytes=0,cause=0;int write=0,has=0; if(kind==XT_ATOMIC) { address=next.cpu.a[s]+((word>>16)<<2);bytes=4;write=1;has=1; } @@ -323,7 +335,7 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo uint32_t form=word&UINT32_C(0xff000f); if (form==0x030000) next.cpu.a[t]=old; else { - if((reg==99 && (value&~UINT32_C(63))) || (reg==231 && (value&1023u))) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); + if((reg==97 && (value&~UINT32_C(1))) || (reg==99 && (value&~UINT32_C(63))) || (reg==231 && (value&1023u))) return reject(fault,ESK_XT_UNSUPPORTED,pc,pc,word); special_write(&next,reg,value);if(form==0x610000) next.cpu.a[t]=old; } next.cpu.pc=pc+3; @@ -364,6 +376,7 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo else joined=(uint64_t)next.cpu.a[s]<<32 | next.cpu.a[t]; next.cpu.a[r]=(uint32_t)(joined>>next.sar); next.cpu.pc=pc+3; } else if (kind==XT_FENCE) { + if(word==0x002000 && next.memctl_enabled) loop_buffer_flush(&next); if(next.region_enabled && (word==0x002000 || word==0x002030)) { unsigned bank=word==0x002030 ? 1u : 0u; memcpy(next.region_active[bank],next.region[bank],sizeof next.region[bank]); @@ -422,27 +435,45 @@ es_status esk_xtensa_window_execute(esk_xtensa_windows *w, const esk_xtensa_memo es_status esk_xtensa_window_step(esk_xtensa_windows *w, const esk_xtensa_memory *memory, unsigned *event, esk_xtensa_fault *fault) { - unsigned char code[3]; unsigned i,width=2; size_t used; uint32_t value; - if (event) *event=0; - if (fault) memset(fault,0,sizeof *fault); - if (!w || !event) return ES_EINVAL; - if (w->irq.enabled) { - es_status st=esk_xtensa_irq_poll(w,event); - if (st!=ES_OK || *event) return st; - if (w->irq.waiting) { *event=18; return ES_OK; } + unsigned char code[3];unsigned i,width=2;size_t used;uint32_t value;esk_xtensa_windows fetched;es_status st;int buffering; + if(event) *event=0; + if(fault) memset(fault,0,sizeof *fault); + if(!w || !event) return ES_EINVAL; + if(w->irq.enabled) { + st=esk_xtensa_irq_poll(w,event); + if(st!=ES_OK || *event) { if(st==ES_OK && w->memctl_enabled) loop_buffer_flush(w);return st; } + if(w->irq.waiting) { *event=18;return ES_OK; } + } + fetched=*w; + buffering=w->memctl_enabled && (w->memctl&1u) && !(w->ps&ESK_XT_PS_EXCM) && w->lbeglend; + if(buffering && (fetched.loopbuf_base!=w->lbeg || fetched.loopbuf_end!=w->lend)) { + loop_buffer_flush(&fetched);fetched.loopbuf_base=w->lbeg;fetched.loopbuf_end=w->lend; } - for (i=0;icpu.pc+i;unsigned cause=0; + for(i=0;icpu.pc+i,offset=address-fetched.loopbuf_base;unsigned cause=0; es_status permission=region_access(w,address,1,2,&cause); + int eligible=buffering && address>=fetched.loopbuf_base && addressexceptions_enabled) return esk_xtensa_raise_exception(w,cause,address,1,event); return reject(fault,cause ? ESK_XT_PROTECTION : ESK_XT_UNSUPPORTED,w->cpu.pc,address,0); } - if (!memory || (!memory->fetch && !memory->read) || - (memory->fetch ? memory->fetch : memory->read)(memory->ctx,address,1,&value)!=ES_OK) - return reject(fault,ESK_XT_FETCH,w->cpu.pc,address,0); + if(eligible && (fetched.loopbuf_valid[offset/32]&(UINT32_C(1)<<(offset%32)))) value=fetched.loopbuf[offset]; + else { + if(!memory || (!memory->fetch && !memory->read) || + (memory->fetch ? memory->fetch : memory->read)(memory->ctx,address,1,&value)!=ES_OK) + return reject(fault,ESK_XT_FETCH,w->cpu.pc,address,0); + if(eligible) { + fetched.loopbuf[offset]=(unsigned char)(value&255u); + fetched.loopbuf_valid[offset/32]|=UINT32_C(1)<<(offset%32); + } + } code[i]=(unsigned char)(value&255u); - if (i==0 && (code[0]&15u)<8) width=3; + if(i==0 && (code[0]&15u)<8) width=3; + } + st=esk_xtensa_window_execute(&fetched,memory,code,width,&used,event,fault); + if(st==ES_OK) { + if(*event && fetched.memctl_enabled) loop_buffer_flush(&fetched); + *w=fetched; } - return esk_xtensa_window_execute(w,memory,code,width,&used,event,fault); + return st; } diff --git a/core/mcu/xtensa/windows.h b/core/mcu/xtensa/windows.h index 93b86d137..a147bce39 100644 --- a/core/mcu/xtensa/windows.h +++ b/core/mcu/xtensa/windows.h @@ -30,6 +30,8 @@ typedef struct esk_xtensa_windows { uint32_t threadptr,misc[4],prid,prid_enabled; uint32_t atomctl,atomctl_enabled; uint32_t region_enabled,region[2][8],region_active[2][8]; + uint32_t memctl,memctl_enabled,loopbuf_base,loopbuf_end,loopbuf_valid[8]; + unsigned char loopbuf[256]; } esk_xtensa_windows; /* event 0: retired; 1..6: window transition; 7/8/9: kernel/user/double exception; * 11..17: accepted interrupt level 1..7; 18: suspended WAITI (step only). @@ -44,6 +46,15 @@ typedef struct esk_xtensa_windows { * configuration. This supplies a constant board/SoC input, not a guessed ESP32 * silicon ID or a live pin-waveform model; it survives supported CPU reset. */ es_status esk_xtensa_prid_configure(esk_xtensa_windows *state,uint32_t value); +/* Opt-in original-ESP32 MEMCTL SR97 bit 0, reset 1, 256-byte loop buffer. + * Fetches in the first 256 bytes of [LBEG,LEND) are captured and replayed. + * This deterministic fill/exception-flush policy is NOT qualified silicon + * fetch geometry, replacement, latency or instruction timing. ISYNC, disable, + * bound writes, CPU reset and exception/interrupt entries invalidate it. + * Region permissions still apply to hits. Unsupported upper MEMCTL bits fail. + * execute has no instruction fetch; step owns buffer fill/replay and rollback. + * No physical Xtensa/device-cache or pipeline timing claim. */ +es_status esk_xtensa_memctl_configure(esk_xtensa_windows *state); /* Original-ESP32 Region Protection Option: eight identity-mapped 512MiB * regions per ITLB/DTLB, attributes reset to 2. Explicit opt-in; CPU reset * resets tables only while enabled. One hardware ring, no Xtensa I/D cache. diff --git a/docs/STATUS.md b/docs/STATUS.md index 6aa6846ed..99a410020 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -10,13 +10,18 @@ does not mean the described behaviour exists. **Current implementation update:** an [explicit eFuse read/reload profile](engineering/esp32-efuse-read-profile.md) now supports None-coded input images, read protection and completion routing. With a supplied synthetic unprogrammed image, real ROM execution advances from -seven to 545 retired instructions, then blocks on RSR.MEMCTL at PC 0x400004f2. +seven to 6,692 retired instructions, then blocks on RTC reset-state read at +0x3ff48034, PC 0x400081df. [MEMCTL](engineering/esp32-memctl-loop-buffer.md) now +has reset/control and a bounded functional loop-buffer profile. Its physical +fill/fetch/pipeline timing remains unqualified. [Region protection](engineering/esp32-region-protection.md) now implements fixed ITLB/DTLB maintenance, synchronized permissions and causes 20/28/29. [ATOMCTL](engineering/esp32-atomic-control.md) now has six-bit privileged register access, reset, memory-type selection, local/RCW atomics and cause-3 dispatch. -Internal cache operations, complete memory-system/loop-buffer behavior and full -SDK boot remain absent. +Internal cache operations, complete memory-system and physical loop-buffer +behavior, and full SDK boot remain absent. The [timing qualification tools](engineering/esp32-instruction-timing.md) +now build a hardware cycle-capture firmware and validate complete logs; no +physical capture or calibrated instruction-time model exists yet. Read completion is an explicit external event; no physical duration is inferred. The earlier synchronous profile took the ROM failure/reset path. The owner's actual fuse image is not inferred. A separate diff --git a/docs/engineering/esp32-boot-verification.md b/docs/engineering/esp32-boot-verification.md index 70f691a46..044ae2b32 100644 --- a/docs/engineering/esp32-boot-verification.md +++ b/docs/engineering/esp32-boot-verification.md @@ -3,7 +3,7 @@ **Result on 2026-11-05: the owner's board reports ESP32-D0WD-V3 revision 3.1, JEDEC C4 60 16 and 4 MB flash. Giantec's GT25Q32A-U datasheet matches that ID; exact package/ordering suffix remains unconfirmed. The pinned SDK example boots -in Espressif QEMU. Native EmbSIM stops at unsupported RSR.MEMCTL after 545 retired +in Espressif QEMU. Native EmbSIM stops at RTC reset-state read after 6,692 retired instructions with the new explicit synthetic eFuse image; without that profile, the baseline stops at an eFuse read after seven.** Neither result establishes successful native boot. @@ -114,7 +114,12 @@ configuration or printed SDK capacity must not substitute for hardware identity. ## Native EmbSIM result: blocked -**Latest follow-up:** [Region protection](esp32-region-protection.md) now completes +**Latest follow-up:** [Functional MEMCTL/loop buffering](esp32-memctl-loop-buffer.md) +now passes initialization and advances to 6,692 retirements, blocked at +RTC_CNTL_RESET_STATE_REG read 0x3ff48034/PC 0x400081df. The buffer policy does not +qualify physical instruction timing. Native SDK boot remains unverified. + +**Prior region-protection result:** [Region protection](esp32-region-protection.md) now completes both instruction/data setup loops and barriers. The explicit-input probe reaches 545 retirements and unsupported RSR.MEMCTL at PC 0x400004f2. Full SDK boot remains unverified. @@ -146,7 +151,7 @@ fault load failure (kind 5), status -1 Pinned SDK `soc/esp32/include/soc/efuse_reg.h` identifies that address as `EFUSE_BLK0_RDATA0_REG`. This baseline deliberately has no eFuse profile attached. With the new explicit image attached, the first observed blocker -moved first to WSR.ATOMCTL, then WITLB, and now RSR.MEMCTL, as recorded above. The earlier synchronous profile took +moved first to WSR.ATOMCTL, then WITLB, then RSR.MEMCTL, and now the RTC reset-state read, as recorded above. The earlier synchronous profile took the ROM failure path and requested a software reset through RTC options; it did not pass the eFuse handshake. RTC/strapping/reset state, further eFuse behavior, dedicated flash diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 78581e590..86e913b48 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -32,7 +32,11 @@ unprogrammed fixture with explicit read-completion events advances ROM to 440 retired instructions in the prior profile. With [atomic-operation control](esp32-atomic-control.md), it now reaches 451 instructions in the prior profile. With [fixed-region protection](esp32-region-protection.md), -it reaches 545 instructions and unsupported RSR.MEMCTL at PC 0x400004f2. It is not the owner's fuse dump and does +it reached 545 instructions in the prior profile. +[Functional MEMCTL/loop buffering](esp32-memctl-loop-buffer.md) now advances it to +6,692 instructions and an RTC reset-state read at 0x3ff48034/PC 0x400081df. +[Instruction timing](esp32-instruction-timing.md) still requires physical evidence; +a buildable capture firmware is prepared without claiming calibrated latency. It is not the owner's fuse dump and does not establish full boot. The [Giantec storage subset](esp32-giantec-profile.md) adds independently checked timing/WEL behavior for the matching JEDEC family; complete chip behavior remains unqualified. diff --git a/docs/engineering/esp32-instruction-timing.md b/docs/engineering/esp32-instruction-timing.md new file mode 100644 index 000000000..2b72c495f --- /dev/null +++ b/docs/engineering/esp32-instruction-timing.md @@ -0,0 +1,65 @@ +# Original ESP32 instruction timing and qualification + +**Current mode: deterministic functional execution, not cycle-accurate original +ESP32 simulation. No instruction latency table is installed.** + +Instruction retirement counts, externally advanced CCOUNT ticks, flash-chip +operation deadlines and artificial test-event schedules are different quantities. +They must not be converted into elapsed CPU time by treating retirement as one +cycle. Clock capability (up to 240 MHz) also does not establish current frequency, +instruction duration, cache waits, bus contention or physical GPIO timing. + +## Current implemented boundaries + +- Selected instruction results, windows, exceptions, interrupts and both core + contexts execute through serialized native callbacks. No issue/retire pipeline, + dependencies, branch penalties or timed bus arbitration is implemented. +- CPU compare timers advance from explicitly supplied processor-clock ticks. + WAITI, APP gate/stall/reset behavior follows the existing bounded clock contract. +- Flash operations advance from separately supplied microseconds with named-part + datasheet duration bounds. These are fixture deadlines, not measured CPU cycles. +- The [MEMCTL loop-buffer profile](esp32-memctl-loop-buffer.md) implements bit 0, + functional fill/replay and invalidation. Its exact fill/flush geometry is an + explicitly selected policy; it is not a measured silicon fetch or timing model. + +## Concrete hardware measurement firmware + +The new [cycle-probe project](../../tests/vendor/xtensa_integer/cycle_probe/README.md) +builds against pinned ESP-IDF v4.4.7 with actual ESP32 GCC. Its nine IRAM kernels +exercise NOP/arithmetic/division, dependencies, local-memory operations and an +atomic pair. It captures both cores sequentially, loop buffer disabled/enabled, +six loop counts and 17 repeats: 3,672 complete kernel observations. + +The capture uses CCOUNT with explicit DSYNC/RSYNC framing and short maskable- +interrupt exclusions. MEMCTL is restored, output is deferred until collection +ends, and no MAC/security data is logged. Counts include framing, setup and +layout; they are not automatically subtracted or converted into instruction +latencies. Peer idle tasks, NMI/debug, clock calibration, flash/peripheral paths +and general bus contention remain separate qualification work. + +The project was compiled successfully and reference-executed in Espressif QEMU +only. QEMU success qualifies firmware/control-flow and output, not physical +cycles. No physical board was programmed or measured in this session. A capture +analyzer rejects incomplete, duplicated, malformed, zero-cycle or falsely +qualified data, preserves outliers, and records provenance as unauthenticated. +Its reports always retain `timing_model_qualified: false`. + +## Requirements before claiming timing support + +1. Collect reproducible physical-board data with the exact silicon revision, + effective clocks, code layout/placement, operands, interrupt/debug conditions + and toolchain recorded. Compare repeated groups and retain their distributions. +2. Qualify instruction classes and dependency/branch/window/exception costs on + internal RAM, separately from external flash, SoC cache state and local/PIF + access paths. Loop-buffer behavior requires its own hardware comparisons. +3. Introduce timed issue/completion and shared-bus events so side effects, + CCOUNT/interrupt deadlines, WAITI, core gates and peripheral time agree. Adding + durations after synchronous writes alone does not establish those semantics. +4. Qualify clock-tree changes, memory wait states, two-core contention, DMA, + cache misses and peripheral activity, then validate complete firmware traces. + Unknown timing must remain explicitly unsupported in any qualified profile. + +There is no fallback table of guessed one-cycle instructions. A future model may +be qualified for a documented subset; that must not be labeled cycle-accurate +whole-board execution until the relevant clock, pipeline and memory behaviors +have independent evidence. diff --git a/docs/engineering/esp32-memctl-loop-buffer.md b/docs/engineering/esp32-memctl-loop-buffer.md new file mode 100644 index 000000000..8bfadc759 --- /dev/null +++ b/docs/engineering/esp32-memctl-loop-buffer.md @@ -0,0 +1,95 @@ +# Original ESP32 MEMCTL and functional loop buffer + +**Implemented: opt-in MEMCTL bit 0, reset/read/write/exchange and a bounded +256-byte functional instruction loop-buffer profile. Silicon fill/replacement, +fetch waveforms, power, pipeline and instruction timing remain unqualified.** + +## Real operation and profile boundary + +The pinned original ESP32 core configuration has a 256-byte loop buffer and +zero Xtensa I/D-cache bytes. Xtensa ISA RI-2021.8 section 4.3.2 and table 191 +specify MEMCTL SR97 bit 0 as loop-buffer enable, reset 1. Clearing it flushes the +buffer; proper instruction-memory synchronization, including ISYNC, flushes too. +The buffer holds part/all of a hardware loop to reduce instruction-memory fetches. +Other MEMCTL cache/snoop fields are not qualified for this configuration; writes +outside bit 0 fail unchanged instead of accepting controls that do nothing. + +`esk_xtensa_memctl_configure` explicitly enables this profile. RSR/WSR/XSR have +the existing privilege/window rules; original-ESP32 one-ring mode remains +privileged. Enabled CPU reset restores bit 0 to 1 and empties valid bytes. +Legacy ABI fixtures remain disabled until opt-in, preserving their previous +instruction-bus behavior. State and buffered bytes belong to each core. + +The ISA summary does not define the physical buffer organization or exact +capture/replacement schedule. This model chooses the following **deterministic +functional policy**, rather than claiming a measured LX6 microarchitecture: + +- Capture fetched bytes in the first 256 bytes of nonwrapping `[LBEG,LEND)` + while enabled and outside EXCM. Longer loops still execute normally; bytes + beyond the prefix use the provider. Mixed-width instructions can cross it. +- Replay valid bytes without another provider fetch. Do not synthesize CPU + cycles, cache-miss penalties or bus transactions from hit/miss counts. +- Invalidate on ISYNC, disable, CPU reset, explicit bound writes, changed bound + tags and exception/interrupt entry. This conservative exception-flush policy + is not asserted as the silicon's exact replacement behavior. +- Preserve the previous CPU/buffer state if fetch or execution fails. Bytes are + staged in a temporary execution state; failed partial instruction fetches and + unsupported decoding do not install new valid bytes. + +Instruction permission checks run before every byte, including hits. The buffer +cannot grant execute access, allocate memory or bypass the region profile. +Exception/interrupt polling precedes fetch, as before. Direct `window_execute` +uses supplied bytes; `window_step` owns actual fetch/capture/replay. An ISYNC or +disable executed from a buffered instruction invalidates its own captured bytes. + +A software write to code does not silently snoop/invalidate this buffer. Proper +self-modifying code must synchronize instruction memory. Behavior before the +required synchronization is not a compatibility guarantee. The selected buffer +policy does not qualify all physical fetch traces or debugger/pipeline effects. + +## Evidence + +`test_xtensa_memctl` passes **268,225 assertions**, including every operand +register/form, upper-bit rejection, privilege/reset, enabled/disabled loops of +2..512 bytes, exact prefix fetch counts for the chosen policy, mixed two/three- +byte instructions across byte 256, self-modifying-code ISYNC, exception/interrupt +flush, bounds, core-state isolation and failed-fetch/decode rollback. + +`run_memctl.py` independently GNU-assembles register operations: **96 comparisons**. +Actual ESP32 GCC then compiles original C with a hardware loop, writes MEMCTL, +executes ISYNC, computes a sum and restores MEMCTL. Both cores, both enable +values and five loop counts pass: **20 calls, 20 compiled hardware loops and +2,332 retired instructions**, through native instruction memory. Host ABI setup +is explicit. No CPU timing is inferred from retirement or provider fetch counts. + +The external real-ROM diagnostic now passes MEMCTL initialization and retires +**6,692 instructions**, then stops at **RTC_CNTL_RESET_STATE_REG** read, +address 0x3ff48034, PC 0x400081df, opcode 0x0828. The +[hash-recorded probe](../../tests/vendor/xtensa_integer/evidence/boot-with-memctl-2026-11-05.json) +still uses external ROM/SDK flash, a synthetic eFuse image, explicit PRID and +artificial completion scheduling. No fallback MMIO or PC shortcut is introduced. +Full ROM-to-application boot, RTC reset/wakeup state and subsequent requirements +remain incomplete. + +## Timing work + +The [timing qualification project](esp32-instruction-timing.md) builds a hardware +CCOUNT measurement firmware and validates complete capture logs. It is groundwork +for physical measurement, not a calibrated simulator timing model. The current +loop-buffer profile must not be described as cycle accurate. + +## References and reproduction + +Xtensa ISA Summary RI-2021.8, loop option and table 191, external PDF SHA-256 +`474c4aaf2d429ff79c48127e738c4110cecd8072133169ad6d00a050edf244b7`. +Original ESP32 `core-isa.h` declares `XCHAL_LOOP_BUFFER_SIZE=256` and zero I/D +cache sizes; SHA-256 +`60d20f76c7730b24041b9f55b16e0062f30bf85c7661c7ed215f050fedcdb344`. + +```sh +python3 tests/vendor/xtensa_integer/run_memctl.py \ + --assembler /path/to/xtensa-esp32-elf-as \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld --nm /path/to/xtensa-esp32-elf-nm +``` diff --git a/docs/engineering/esp32-region-protection.md b/docs/engineering/esp32-region-protection.md index 8a7ab9ec9..903ee1117 100644 --- a/docs/engineering/esp32-region-protection.md +++ b/docs/engineering/esp32-region-protection.md @@ -101,7 +101,11 @@ architecturally non-removing invalidate behavior for these fixed tables. - Existing flat-memory, windows, exceptions, atomic, timer, TLS, reset, mapped flash, SPI programming and eFuse compiler/vendor fixtures also rerun. -The external real-ROM probe completes both instruction/data maintenance loops +The subsequent [MEMCTL profile](esp32-memctl-loop-buffer.md) now reaches RTC +reset-state read after 6,692 instructions. The historical region-only result +below retains its original source scope. + +The external real-ROM probe at this milestone completes both instruction/data maintenance loops and ISYNC/DSYNC, retires **545 instructions**, then stops at unsupported **RSR.MEMCTL**, opcode 0x036120, PC 0x400004f2. The [hash-recorded result](../../tests/vendor/xtensa_integer/evidence/boot-with-regions-2026-11-05.json) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 36e869a13..5858b159c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -20,6 +20,8 @@ add_test(NAME catalog_fixtures COMMAND embsim_cli catalog validate ${CMAKE_SOURC # protocol conformance suite over the stdio and WebSocket transports. find_package(Python3 COMPONENTS Interpreter QUIET) if(Python3_Interpreter_FOUND) + add_test(NAME esp32_cycle_capture COMMAND ${Python3_EXECUTABLE} + ${CMAKE_SOURCE_DIR}/tests/vendor/xtensa_integer/cycle_probe/test_analyze.py) add_test(NAME esp32_image COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tests/protocol/test_esp32_image.py --embsim $ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) add_test(NAME electrical_feedback COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tests/protocol/test_electrical_feedback.py diff --git a/tests/unit/test_xtensa_memctl.c b/tests/unit/test_xtensa_memctl.c new file mode 100644 index 000000000..732da4c6f --- /dev/null +++ b/tests/unit/test_xtensa_memctl.c @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/xtensa/windows.h" +#define BASE UINT32_C(0x40080000) +typedef struct fixture { unsigned char code[1024];unsigned reads;uint32_t fail; } fixture; +static es_status fetch(void *ctx,uint32_t address,unsigned bytes,uint32_t *value) +{ + fixture *f=(fixture *)ctx;f->reads++; + if(bytes!=1 || address=sizeof f->code || address==f->fail) return ES_EINVAL; + *value=f->code[address-BASE];return ES_OK; +} +static es_status execute(esk_xtensa_windows *w,uint32_t word,unsigned *event) +{ + unsigned char code[3];size_t used;esk_xtensa_fault fault; + code[0]=(unsigned char)(word&255u);code[1]=(unsigned char)((word>>8)&255u);code[2]=(unsigned char)((word>>16)&255u); + return esk_xtensa_window_execute(w,NULL,code,3,&used,event,&fault); +} +static void setup(esk_xtensa_windows *w,fixture *f) +{ + unsigned i;esk_xtensa_windows_init(w,BASE,NULL);CHECK_EQ(esk_xtensa_memctl_configure(w),ES_OK); + memset(f,0,sizeof *f);for(i=0;icode;i+=2) { f->code[i]=0x3d;f->code[i+1]=0xf0; } + w->ps=0;w->lbeg=BASE;w->lend=BASE+256;w->lcount=1; +} +static void register_contract(void) +{ + unsigned reg,form,value,event,bit;esk_xtensa_windows w,before;fixture f; + const uint32_t forms[3]={0x036100,0x136100,0x616100}; + CHECK_EQ(esk_xtensa_memctl_configure(NULL),ES_EINVAL); + esk_xtensa_windows_init(&w,BASE,NULL);before=w; + CHECK_EQ(execute(&w,0x036120,&event),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&w,&before,sizeof w),0); + for(reg=0;reg<16;reg++) for(form=0;form<3;form++) for(value=0;value<2;value++) { + setup(&w,&f);w.memctl=1-value;w.cpu.a[reg]=value; + CHECK_EQ(execute(&w,forms[form]|(reg<<4),&event),ES_OK);CHECK_EQ(event,0); + CHECK_EQ(w.memctl,form ? value : 1-value);CHECK_EQ(w.cpu.a[reg],form==1 ? value : 1-value); + } + for(bit=1;bit<32;bit++) { + setup(&w,&f);w.cpu.a[2]=UINT32_C(1)<256 ? (length-256)*2 : 0)); + w.cpu.a[2]=0;CHECK_EQ(execute(&w,0x136120,&event),ES_OK); + for(i=0;i<8;i++) CHECK_EQ(w.loopbuf_valid[i],0); + w.cpu.pc=BASE;w.lcount=1;f.reads=0; + for(step=0;step +#include +#include +#include "esp_attr.h" +#include "esp_chip_info.h" +#include "esp_idf_version.h" +#include "esp_private/esp_clk.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#define REPEATS 17 +#define COUNTS 6 +#define CASES 9 +static const uint32_t counts[COUNTS]={1,4,16,64,256,1024}; +static const char *const names[CASES]={"nop","addi_dependent","mull_dependent","quou_independent","quou_dependent","l32i_dram","s32i_dram","l32ai_dram","s32c1i_match_pair"}; +static uint32_t samples[2][2][COUNTS][CASES][REPEATS]; +static uint32_t scratch[2]; +static TaskHandle_t supervisor; +static portMUX_TYPE capture_lock=portMUX_INITIALIZER_UNLOCKED; +/* Volatile assembly is the measurement boundary. All cases share the framing, + * but layout and operand dependencies remain part of the recorded kernel. */ +#define MEASURE(body) do { \ + __asm__ volatile("dsync; rsr.ccount %[start]; rsync; loop %[n],1f\n" \ + ".begin no-transform\n" body "\n.end no-transform\n1: dsync; rsr.ccount %[end]; rsync" \ + : [start] "=&a"(start),[end] "=&a"(end),[value] "+a"(value) \ + : [n] "a"(iterations),[address] "a"(address),[operand] "a"(operand) \ + : "memory"); \ +} while(0) +static uint32_t IRAM_ATTR __attribute__((noinline)) measure(unsigned kind,uint32_t iterations,uint32_t *address) +{ + uint32_t start,end,value=kind==8 ? 0u : UINT32_C(0x12345678),operand=3; + switch(kind) { + case 0:MEASURE("nop");break; + case 1:MEASURE("addi %[value],%[value],1");break; + case 2:MEASURE("mull %[value],%[value],%[operand]");break; + case 3:MEASURE("quou %[value],%[n],%[operand]");break; + case 4:MEASURE("quou %[value],%[value],%[operand]");break; + case 5:MEASURE("l32i %[value],%[address],0");break; + case 6:MEASURE("s32i %[value],%[address],0");break; + case 7:MEASURE("l32ai %[value],%[address],0");break; + default:MEASURE("wsr.scompare1 %[value]; s32c1i %[value],%[address],0");break; + } + return end-start; /* Defined modulo 2^32; each bounded kernel is short. */ +} +static void capture(void *argument) +{ + unsigned cpu=(unsigned)(uintptr_t)argument,mode,c,k,r; + for(mode=0;mode<2;mode++) for(c=0;c Date: Thu, 5 Nov 2026 17:52:17 +0000 Subject: [PATCH 33/57] Invalidate loop buffering through direct CPU interrupt entry --- CHANGELOG.md | 2 +- core/mcu/xtensa/interrupts.c | 1 + core/mcu/xtensa/windows.c | 2 +- docs/engineering/esp32-memctl-loop-buffer.md | 2 +- tests/unit/test_xtensa_memctl.c | 4 ++++ .../xtensa_integer/evidence/boot-with-memctl-2026-11-05.json | 4 ++-- 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b70605268..58fbdfde4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ versions follow semantic versioning. There is no release yet. exception invalidation, protected hits and failed-fetch/decode rollback. Exact silicon fill/replacement, fetch geometry and instruction timing remain unqualified. Legacy fixtures retain explicit opt-in. -- 268,225 native assertions, 96 GNU register comparisons and 20 compiled C +- 268,232 native assertions, 96 GNU register comparisons and 20 compiled C hardware-loop calls pass. Native ROM reaches 6,692 instructions, then blocks on RTC reset-state read at 0x3ff48034, PC 0x400081df. SDK boot remains unverified. - Added buildable ESP-IDF cycle-capture firmware and strict log analysis for diff --git a/core/mcu/xtensa/interrupts.c b/core/mcu/xtensa/interrupts.c index 3a6cab803..0f4527ca6 100644 --- a/core/mcu/xtensa/interrupts.c +++ b/core/mcu/xtensa/interrupts.c @@ -59,6 +59,7 @@ es_status esk_xtensa_irq_poll(struct esk_xtensa_windows *w,unsigned *event) w->irq.epc[level]=w->cpu.pc;w->irq.eps[level]=w->ps; w->ps=(w->ps&~15u)|level|ESK_XT_PS_EXCM;w->cpu.pc=w->vecbase_enabled ? w->vecbase+0x180u+(level-2u)*64u : w->irq.vectors[level]; } + if(w->memctl_enabled) memset(w->loopbuf_valid,0,sizeof w->loopbuf_valid); if (level==7) w->irq.latched&=~NMI; w->irq.waiting=0;w->irq.last_level=level;w->irq.last_pending=mask; *event=10u+level;return ES_OK; diff --git a/core/mcu/xtensa/windows.c b/core/mcu/xtensa/windows.c index fb4810d43..7a058913d 100644 --- a/core/mcu/xtensa/windows.c +++ b/core/mcu/xtensa/windows.c @@ -441,7 +441,7 @@ es_status esk_xtensa_window_step(esk_xtensa_windows *w, const esk_xtensa_memory if(!w || !event) return ES_EINVAL; if(w->irq.enabled) { st=esk_xtensa_irq_poll(w,event); - if(st!=ES_OK || *event) { if(st==ES_OK && w->memctl_enabled) loop_buffer_flush(w);return st; } + if(st!=ES_OK || *event) return st; if(w->irq.waiting) { *event=18;return ES_OK; } } fetched=*w; diff --git a/docs/engineering/esp32-memctl-loop-buffer.md b/docs/engineering/esp32-memctl-loop-buffer.md index 8bfadc759..af707243b 100644 --- a/docs/engineering/esp32-memctl-loop-buffer.md +++ b/docs/engineering/esp32-memctl-loop-buffer.md @@ -49,7 +49,7 @@ policy does not qualify all physical fetch traces or debugger/pipeline effects. ## Evidence -`test_xtensa_memctl` passes **268,225 assertions**, including every operand +`test_xtensa_memctl` passes **268,232 assertions**, including every operand register/form, upper-bit rejection, privilege/reset, enabled/disabled loops of 2..512 bytes, exact prefix fetch counts for the chosen policy, mixed two/three- byte instructions across byte 256, self-modifying-code ISYNC, exception/interrupt diff --git a/tests/unit/test_xtensa_memctl.c b/tests/unit/test_xtensa_memctl.c index 732da4c6f..b3f499204 100644 --- a/tests/unit/test_xtensa_memctl.c +++ b/tests/unit/test_xtensa_memctl.c @@ -106,6 +106,10 @@ static void mixed_width_and_self_modifying_flush(void) CHECK_EQ(esk_xtensa_window_step(&w,&bus,&event,&fault),ES_OK);CHECK(w.loopbuf_valid[0]!=0); w.irq.intenable=UINT32_C(1)<<19;CHECK_EQ(esk_xtensa_irq_line(&w,19,1),ES_OK); CHECK_EQ(esk_xtensa_window_step(&w,&bus,&event,&fault),ES_OK);CHECK_EQ(event,12);CHECK_EQ(w.loopbuf_valid[0],0); + CHECK_EQ(esk_xtensa_lx6_reset(&w,1),ES_OK);w.cpu.pc=BASE;w.ps=0; + CHECK_EQ(esk_xtensa_window_step(&w,&bus,&event,&fault),ES_OK);CHECK(w.loopbuf_valid[0]!=0); + w.irq.intenable=UINT32_C(1)<<19;CHECK_EQ(esk_xtensa_irq_line(&w,19,1),ES_OK); + CHECK_EQ(esk_xtensa_irq_poll(&w,&event),ES_OK);CHECK_EQ(event,12);CHECK_EQ(w.loopbuf_valid[0],0); } TEST_MAIN_BEGIN (void)t_str_eq;RUN(register_contract);RUN(prefix_capacity_and_disable);RUN(barriers_bounds_reset_and_isolation);RUN(failed_fetch_and_decode_rollback);RUN(mixed_width_and_self_modifying_flush); diff --git a/tests/vendor/xtensa_integer/evidence/boot-with-memctl-2026-11-05.json b/tests/vendor/xtensa_integer/evidence/boot-with-memctl-2026-11-05.json index fef0ffdba..281827865 100644 --- a/tests/vendor/xtensa_integer/evidence/boot-with-memctl-2026-11-05.json +++ b/tests/vendor/xtensa_integer/evidence/boot-with-memctl-2026-11-05.json @@ -83,10 +83,10 @@ "next_blocker": "RTC_CNTL_RESET_STATE_REG read at 0x3ff48034, PC 0x400081df; native SDK boot remains unverified", "fixture_note": "Explicit synthetic fuse image, PRID, region/functional MEMCTL profiles and artificial completion schedule; no physical timing or owner fuse image claim", "sources_sha256": { - "core/mcu/xtensa/windows.c": "e5a7bd37c367101255c4a24ab4b59728ca452ccf72b4d4ce696bc097fea5e238", + "core/mcu/xtensa/windows.c": "0d642d048a310250f5954f9a4ae41b6a1e2cb0888ed1afdcf20a9165dc39a0e9", "core/mcu/xtensa/windows.h": "159fdbcd13da216e15fe64bab2cd70c24950b6e173d06d5643a323782aecd067", "core/mcu/xtensa/execute.c": "c69879eb07ab393416c396a6789c5f638d77810016a2932c58e51b71e2ba7fa7", - "core/mcu/xtensa/interrupts.c": "429607ba6873226a3be8d19ec420595ff2238964e4896365fc8165e669f0b031", + "core/mcu/xtensa/interrupts.c": "e5b4fbefa1b249847d5ec298893fd431f5f11673d150b0b7c92e162bc3d0055f", "tests/vendor/xtensa_integer/probe_rom_boot.py": "e5a0a431d3fc90995ee9c260d35b6930b2bd77174df502eb8d2937cb4e049b1d" } } From e8e5034fb76a7592526b56b11eac49ade0d70b3d Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 18:03:21 +0000 Subject: [PATCH 34/57] Model ESP32 RTC reset-state latches and retained registers --- CHANGELOG.md | 14 +++ core/mcu/esp32/machine.c | 34 ++++++ core/mcu/esp32/machine.h | 12 ++ docs/STATUS.md | 6 +- docs/engineering/esp32-boot-verification.md | 12 +- docs/engineering/esp32-dual-core.md | 4 +- docs/engineering/esp32-memctl-loop-buffer.md | 7 +- docs/engineering/esp32-rtc-reset-retention.md | 106 ++++++++++++++++++ tests/unit/test_esp32_rtc.c | 84 ++++++++++++++ tests/vendor/README.md | 6 + .../evidence/boot-with-rtc-2026-11-05.json | 92 +++++++++++++++ tests/vendor/xtensa_integer/probe_rom_boot.py | 5 + tests/vendor/xtensa_integer/run_rtc.py | 90 +++++++++++++++ 13 files changed, 465 insertions(+), 7 deletions(-) create mode 100644 docs/engineering/esp32-rtc-reset-retention.md create mode 100644 tests/unit/test_esp32_rtc.c create mode 100644 tests/vendor/xtensa_integer/evidence/boot-with-rtc-2026-11-05.json create mode 100644 tests/vendor/xtensa_integer/run_rtc.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 58fbdfde4..98b1432b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Original ESP32 RTC reset state and retention + +- Added an explicit RTC reset-state/eight-word retention snapshot profile. + Cause fields are read-only; vector selects and retention are writable. + DPORT APP reset records reason 14, samples its RTC vector and preserves + RTC/retention and peer state, applying reset after instruction commit. +- 53,787 native assertions and 1,281 pinned field/retention/reset comparisons + pass; the unchanged external PRO-ROM reset-reason helper passes 256 calls. +- With an additional explicit synthetic power-on RTC snapshot, native ROM + reaches 6,698 instructions and blocks on analog configuration read 0x3ff48088. + Unknown RTC controls, global/power/watchdog/wakeup behavior, native SDK boot + and physical instruction timing remain unqualified. + + ### Original ESP32 MEMCTL and timing qualification tools - Added opt-in MEMCTL bit 0 with reset, privileged read/write/exchange and a diff --git a/core/mcu/esp32/machine.c b/core/mcu/esp32/machine.c index bf2079639..bcc0403e6 100644 --- a/core/mcu/esp32/machine.c +++ b/core/mcu/esp32/machine.c @@ -8,6 +8,7 @@ struct esk_esp32_machine { esk_xtensa_windows cpu[2];uint64_t retired[2];unsigned running[2],executing; uint32_t app_clock,app_stall,app_boot,app_reset,app_reset_enabled,app_reset_vector,app_reset_pending; unsigned spi1_owned,efuse_owned; + uint32_t rtc_enabled,rtc_state,rtc_store[8]; esk_esp32_matrix matrix;struct machine_view view[2]; }; /* Never mutate an executing CPU from a bus callback. Synchronize the @@ -38,13 +39,38 @@ static void sync_inputs(esk_esp32_machine *m) static void apply_reset(esk_esp32_machine *m) { if(m->app_reset_pending) { + if(m->rtc_enabled) { + m->app_reset_vector=(m->rtc_state>>12)&1u; + m->rtc_state=(m->rtc_state&~UINT32_C(0xfc0))|(14u<<6); + } (void)esk_xtensa_lx6_reset(&m->cpu[1],m->app_reset_vector);m->app_reset_pending=0; } } +/* Return 0=reset state, 1..8=retention, -1=unqualified/hole. */ +static int rtc_register(const esk_esp32_machine *m,uint32_t address) +{ + if(!m->rtc_enabled) return -1; + if(address>=0x3ff48034 && address<0x3ff48038) return 0; + if(address>=0x3ff4804c && address<0x3ff4805c) return 1+(int)((address-0x3ff4804c)/4u); + if(address>=0x3ff480b0 && address<0x3ff480c0) return 5+(int)((address-0x3ff480b0)/4u); + return -1; +} +es_status esk_esp32_machine_rtc_configure(esk_esp32_machine *m,uint32_t reset_state,const uint32_t *retention,size_t count) +{ + if(!m || m->executing || (!retention && count)) return ES_EINVAL; + if(!retention && !count) { m->rtc_enabled=0;return ES_OK; } + if(count!=8 || (reset_state&~UINT32_C(0x3fff))) return ES_EINVAL; + memcpy(m->rtc_store,retention,sizeof m->rtc_store);m->rtc_state=reset_state;m->rtc_enabled=1;return ES_OK; +} /* APP reset input is opt-in. BOOT_ADDR is consumed by firmware, never used * as the hardware reset PC. Architectural changes occur outside callbacks. */ static es_status control_read(const esk_esp32_machine *m,uint32_t address,unsigned bytes,uint32_t *value) { + int reg=rtc_register(m,address); + if(reg>=0) { + if(!value || bytes!=4 || (address&3u)) return ES_EINVAL; + *value=reg ? m->rtc_store[(unsigned)reg-1u] : m->rtc_state;return ES_OK; + } if(address<0x3ff0002c || address>0x3ff0003b) return ES_EUNSUPPORTED; if(address<0x3ff00030 && !m->app_reset_enabled) return ES_EUNSUPPORTED; if(!value || bytes!=4 || (address&3u)) return ES_EINVAL; @@ -53,6 +79,13 @@ static es_status control_read(const esk_esp32_machine *m,uint32_t address,unsign } static es_status control_write(esk_esp32_machine *m,uint32_t address,unsigned bytes,uint32_t value) { + int reg=rtc_register(m,address); + if(reg>=0) { + if(bytes!=4 || (address&3u)) return ES_EINVAL; + if(reg) m->rtc_store[(unsigned)reg-1u]=value; + else m->rtc_state=(m->rtc_state&0xfffu)|(value&0x3000u); + return ES_OK; + } if(address<0x3ff0002c || address>0x3ff0003b) return ES_EUNSUPPORTED; if(address<0x3ff00030 && !m->app_reset_enabled) return ES_EUNSUPPORTED; if(bytes!=4 || (address&3u)) return ES_EINVAL; @@ -136,6 +169,7 @@ es_status esk_esp32_machine_nmi_mask(esk_esp32_machine *m,unsigned cpu,int asser es_status esk_esp32_machine_app_reset_configure(esk_esp32_machine *m,unsigned vector_input) { if(!m || vector_input>1 || m->executing) return ES_EINVAL; + if(m->rtc_enabled) m->rtc_state=(m->rtc_state&~UINT32_C(0x1000))|(vector_input<<12); m->app_reset_enabled=1;m->app_reset_vector=vector_input;m->app_reset=1;m->app_reset_pending=1; apply_reset(m);return ES_OK; } diff --git a/core/mcu/esp32/machine.h b/core/mcu/esp32/machine.h index 9334e819f..7c011f2b1 100644 --- a/core/mcu/esp32/machine.h +++ b/core/mcu/esp32/machine.h @@ -34,6 +34,18 @@ esk_xtensa_memory esk_esp32_machine_bus(esk_esp32_machine *m,unsigned cpu); es_status esk_esp32_machine_source(esk_esp32_machine *m,unsigned source,int asserted); es_status esk_esp32_machine_gpio_irq(esk_esp32_machine *m,unsigned cpu,int nmi,int asserted); es_status esk_esp32_machine_nmi_mask(esk_esp32_machine *m,unsigned cpu,int asserted); +/* Opt-in RTC reset-state and eight retention-register snapshot. All eight + * initial words are explicit because hardware power-on contents are undefined. + * reset_state supplies only bits 13:0: vector selects and raw cause latches; + * no cause/silicon/clock identity is inferred. NULL/0 detaches host fixture. + * RESET_STATE cause fields are RO, vector bits RW; STORE0..7 full-width RW. + * DPORT APP reset updates APP cause to 14 and samples the RTC vector on assert. + * Retention/state survive the bounded CPU-only reset. PRO reset generation, + * RTC software/watchdog/global/deep-sleep/power reset and wakeup/timing remain + * unsupported. Direct low-level CPU reset is a debug bypass, not an RTC event. + * Configuration and host bus use are between turns; no profile infers a board. */ +es_status esk_esp32_machine_rtc_configure(esk_esp32_machine *m,uint32_t reset_state, + const uint32_t *retention,size_t count); /* Opt into the supported APP reset-input profile, asserting reset now. * Select static vector input 0/1 explicitly; no board strap inference. DPORT * 0x3ff0002c bit 0 then holds/releases APP. Assertion applies the supported diff --git a/docs/STATUS.md b/docs/STATUS.md index 99a410020..a225befd6 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -10,8 +10,10 @@ does not mean the described behaviour exists. **Current implementation update:** an [explicit eFuse read/reload profile](engineering/esp32-efuse-read-profile.md) now supports None-coded input images, read protection and completion routing. With a supplied synthetic unprogrammed image, real ROM execution advances from -seven to 6,692 retired instructions, then blocks on RTC reset-state read at -0x3ff48034, PC 0x400081df. [MEMCTL](engineering/esp32-memctl-loop-buffer.md) now +seven to 6,692 retired instructions without an RTC snapshot. With the new +[RTC reset/retention profile](engineering/esp32-rtc-reset-retention.md) and an +explicit synthetic power-on snapshot, it reaches 6,698 instructions, then blocks +on analog configuration read at 0x3ff48088, PC 0x4000770a. [MEMCTL](engineering/esp32-memctl-loop-buffer.md) now has reset/control and a bounded functional loop-buffer profile. Its physical fill/fetch/pipeline timing remains unqualified. [Region protection](engineering/esp32-region-protection.md) now implements fixed diff --git a/docs/engineering/esp32-boot-verification.md b/docs/engineering/esp32-boot-verification.md index 044ae2b32..0b9d712ff 100644 --- a/docs/engineering/esp32-boot-verification.md +++ b/docs/engineering/esp32-boot-verification.md @@ -3,8 +3,8 @@ **Result on 2026-11-05: the owner's board reports ESP32-D0WD-V3 revision 3.1, JEDEC C4 60 16 and 4 MB flash. Giantec's GT25Q32A-U datasheet matches that ID; exact package/ordering suffix remains unconfirmed. The pinned SDK example boots -in Espressif QEMU. Native EmbSIM stops at RTC reset-state read after 6,692 retired -instructions with the new explicit synthetic eFuse image; without that profile, +in Espressif QEMU. Native EmbSIM stops at an RTC analog-configuration read after 6,698 retired +instructions with explicit synthetic eFuse and RTC snapshots; without an eFuse profile, the baseline stops at an eFuse read after seven.** Neither result establishes successful native boot. @@ -114,7 +114,13 @@ configuration or printed SDK capacity must not substitute for hardware identity. ## Native EmbSIM result: blocked -**Latest follow-up:** [Functional MEMCTL/loop buffering](esp32-memctl-loop-buffer.md) +**Latest follow-up:** [RTC reset-state/retention](esp32-rtc-reset-retention.md) +now supplies protected causes/vector latches and retained words from an explicit +snapshot. With the synthetic power-on fixture, the probe reaches 6,698 retirements +and RTC_CNTL_ANA_CONF_REG read 0x3ff48088/PC 0x4000770a. Native boot remains blocked; +the snapshot is not a capture of owner RTC state. + +**Prior MEMCTL-only result:** [Functional MEMCTL/loop buffering](esp32-memctl-loop-buffer.md) now passes initialization and advances to 6,692 retirements, blocked at RTC_CNTL_RESET_STATE_REG read 0x3ff48034/PC 0x400081df. The buffer policy does not qualify physical instruction timing. Native SDK boot remains unverified. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 86e913b48..263a398b1 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -34,7 +34,9 @@ retired instructions in the prior profile. With in the prior profile. With [fixed-region protection](esp32-region-protection.md), it reached 545 instructions in the prior profile. [Functional MEMCTL/loop buffering](esp32-memctl-loop-buffer.md) now advances it to -6,692 instructions and an RTC reset-state read at 0x3ff48034/PC 0x400081df. +6,692 instructions without an RTC snapshot. Adding the +[explicit RTC reset/retention fixture](esp32-rtc-reset-retention.md) reaches 6,698 +and the next analog-configuration read at 0x3ff48088/PC 0x4000770a. [Instruction timing](esp32-instruction-timing.md) still requires physical evidence; a buildable capture firmware is prepared without claiming calibrated latency. It is not the owner's fuse dump and does not establish full boot. The [Giantec storage subset](esp32-giantec-profile.md) diff --git a/docs/engineering/esp32-memctl-loop-buffer.md b/docs/engineering/esp32-memctl-loop-buffer.md index af707243b..18df5aa28 100644 --- a/docs/engineering/esp32-memctl-loop-buffer.md +++ b/docs/engineering/esp32-memctl-loop-buffer.md @@ -62,7 +62,12 @@ values and five loop counts pass: **20 calls, 20 compiled hardware loops and 2,332 retired instructions**, through native instruction memory. Host ABI setup is explicit. No CPU timing is inferred from retirement or provider fetch counts. -The external real-ROM diagnostic now passes MEMCTL initialization and retires +The subsequent [RTC reset/retention profile](esp32-rtc-reset-retention.md), with +an additional explicit synthetic snapshot, now advances to analog configuration +read after 6,698 instructions. The historical MEMCTL-only result below retains +its original source/fixture scope. + +The external real-ROM diagnostic at this milestone passes MEMCTL initialization and retires **6,692 instructions**, then stops at **RTC_CNTL_RESET_STATE_REG** read, address 0x3ff48034, PC 0x400081df, opcode 0x0828. The [hash-recorded probe](../../tests/vendor/xtensa_integer/evidence/boot-with-memctl-2026-11-05.json) diff --git a/docs/engineering/esp32-rtc-reset-retention.md b/docs/engineering/esp32-rtc-reset-retention.md new file mode 100644 index 000000000..6f22da483 --- /dev/null +++ b/docs/engineering/esp32-rtc-reset-retention.md @@ -0,0 +1,106 @@ +# Original ESP32 RTC reset state and retention registers + +**Implemented: opt-in explicit reset-state/retention snapshots, reset-vector +latches, protected reset causes and DPORT APP-reset integration. This is not a +complete RTC, power/clock tree, wakeup or global reset-domain model.** + +## Initial state and real registers + +ESP32 TRM v5.9 chapters 7/9 distinguish CPU, digital-core and whole-system reset. +A CPU reset does not reset RTC state or clear RAM. RESET_STATE at 0x3ff48034 has +PRO/APP six-bit cause latches and two reset-vector selects. Its summary table +labels the register RO, but individual fields in register 9.12 and the pinned SDK +header specify the vector-select bits as R/W; cause fields are RO. This profile +follows those field definitions. + +`esk_esp32_machine_rtc_configure` supplies an explicit 14-bit reset-state snapshot +and **all eight STORE words**. There is no inferred power-on cause, crystal value, +owner reset history or undefined retention content. Initial cause patterns are +raw fixture inputs, including patterns reserved by hardware; accepting a snapshot +is not claiming a producer can generate every pattern. Unknown upper snapshot +bits or an incomplete word list fail unchanged. NULL/0 detaches this host fixture, +not a physical RTC erase or power action. + +| Register | Behavior | +|---|---| +| RESET_STATE bits 5:0 | PRO cause, software read-only | +| RESET_STATE bits 11:6 | APP cause, software read-only | +| RESET_STATE bits 13:12 | PRO/APP vector selects, R/W | +| STORE0..3, 0x4c..0x58 | Shared full-width retention words | +| STORE4..7, 0xb0..0xbc | Shared full-width retention words | + +Offsets in the table are relative to 0x3ff48000. Reserved RESET_STATE write bits +are ignored and read zero; cause writes do not overwrite latches. MMIO requires +aligned 32-bit data accesses. Narrow/unaligned operations fail, unknown RTC +registers remain unsupported, and no RTC fetch/CAS path is added. Both CPU data +views see the same latches and retention words. + +## Reset and vector integration + +The existing DPORT APP-reset input now records APP reason **14** when an assertion +is applied, preserving PRO cause, vector flags and retention. This reason matches +TRM chapter 7 and SDK `EXT_CPU_RESET`. Reset still applies outside the bus callback, +after the writing instruction commits; the peer's CPU state is not mutated during +execution. Register changes and retained words are visible through the normal +machine bus. + +The APP vector flag is sampled when this bounded reset is asserted: 1 selects +0x40000400, 0 selects 0x50000000. Subsequent flag writes select the next assertion, +not a live PC jump. The existing host `app_reset_configure` selects the initial +flag and asserts its reset input. This is fixture setup; exact electrical sampling +while reset is held is unqualified. BOOT_ADDR remains firmware-consumed data. + +PRO's vector flag has ordinary latch/readback behavior, but no new PRO-reset +producer is claimed. Direct low-level CPU reset APIs bypass this machine event +model and do not synthesize RTC causes. RTC software-reset commands, watchdog, +brownout, deep sleep, wakeup, digital-core/global power-reset domains, analog +configuration, clocks and timing remain unsupported. No status values are supplied +for those devices merely to advance firmware. + +## Evidence + +`test_esp32_rtc`: **53,787 assertions** across all raw cause-field combinations, +vector writes, read-only protection, both-core shared retention, immutable input, +rejected snapshots/accesses, CPU-reset retention/vector/cause behavior and fetched +DPORT reset-store commit. These raw field combinations qualify widths/readback, +not 4,096 physically supported reset-source combinations. + +`run_rtc.py` hash-pins SDK RTC register definitions, reset-reason enums, ROM linker +symbols and an external Espressif QEMU-distributed PRO ROM. It passes **1,281 +independent field/retention/reset comparisons**, then calls the unchanged external +`rtc_get_reset_reason` routine for both selected cause fields on both execution +contexts: **256 calls / 1,792 retired instructions**, through native buses. Host +ABI call setup is explicit. This exercises the supplied PRO-ROM routine; it is +not APP-specific ROM qualification, complete SDK boot or a physical RTC capture. +No ROM bytes are embedded in the runner/application/repository. + +With `--rtc-power-on-fixture`, the diagnostic explicitly supplies reason 1 for +each core, both vector selects 1 and eight zero retention words. These are chosen +snapshot inputs, not measured owner state or automatic defaults. The same external +ROM/SDK image now retires **6,698 instructions**, then fails at +**RTC_CNTL_ANA_CONF_REG** read 0x3ff48088, PC 0x4000770a, opcode 0x0258. The +[recorded result](../../tests/vendor/xtensa_integer/evidence/boot-with-rtc-2026-11-05.json) +remains blocked. Without that additional fixture, the existing 6,692-instruction +reset-state-read failure remains valid. No unknown-MMIO fallback or PC shortcut +is introduced; native application boot and physical timing remain unverified. + +## References and reproduction + +ESP-IDF v4.4.7 external inputs: + +- `soc/esp32/include/soc/rtc_cntl_reg.h`, SHA-256 + `c2d17880e08b28288fba95c3daa48f9a8672722fd56a5616728ee025dc61e0fb`. +- `esp_rom/include/esp32/rom/rtc.h`, SHA-256 + `90bb6c9a0d497a264041c0ac7abdd0475375edcae812868a9deaed0044994f44`. +- `esp_rom/esp32/ld/esp32.rom.ld`, SHA-256 + `0160cb9e14e83de87ad7363bd4977a9dd353b22e868a9bd4474e167ee86e58ec`. + +```sh +python3 tests/vendor/xtensa_integer/run_rtc.py \ + --rtc-reg /path/to/rtc_cntl_reg.h --rtc-header /path/to/rom/rtc.h \ + --rom-symbols /path/to/esp32.rom.ld --rom /path/to/esp32-v3-rom.bin +``` + +Use the [boot probe](esp32-boot-verification.md) with its existing ROM/flash/eFuse +inputs plus `--rtc-power-on-fixture` to reproduce the diagnostic. The complete +RTC/power domain must be qualified separately before claiming whole-board support. diff --git a/tests/unit/test_esp32_rtc.c b/tests/unit/test_esp32_rtc.c new file mode 100644 index 000000000..6fbe68b03 --- /dev/null +++ b/tests/unit/test_esp32_rtc.c @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/machine.h" +static uint32_t store_address(unsigned i) { return i<4 ? 0x3ff4804cu+i*4u : 0x3ff480b0u+(i-4u)*4u; } +static void reset_snapshot_and_fields(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_xtensa_memory bus;uint32_t stores[8],value=0;unsigned pro,app,flags,i; + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK);bus=esk_esp32_machine_bus(m,0); + CHECK_EQ(bus.read(bus.ctx,0x3ff48034,4,&value),ES_EUNSUPPORTED); + for(i=0;i<8;i++) stores[i]=0xa5000000u+i; + for(pro=0;pro<64;pro++) for(app=0;app<64;app++) { + uint32_t causes=pro|(app<<6); + CHECK_EQ(esk_esp32_machine_rtc_configure(m,causes|0x3000u,stores,8),ES_OK); + for(flags=0;flags<4;flags++) { + CHECK_EQ(bus.write(bus.ctx,0x3ff48034,4,0xffffcfffu|(flags<<12)),ES_OK); + CHECK_EQ(bus.read(bus.ctx,0x3ff48034,4,&value),ES_OK);CHECK_EQ(value,causes|(flags<<12)); + } + } + CHECK_EQ(esk_esp32_machine_rtc_configure(m,0x3041,stores,8),ES_OK); + CHECK_EQ(esk_esp32_machine_rtc_configure(m,0x4000,stores,8),ES_EINVAL); + CHECK_EQ(bus.read(bus.ctx,0x3ff48034,4,&value),ES_OK);CHECK_EQ(value,0x3041); + CHECK_EQ(esk_esp32_machine_rtc_configure(m,0,NULL,8),ES_EINVAL); + CHECK_EQ(esk_esp32_machine_rtc_configure(m,0,stores,7),ES_EINVAL); + for(i=0;i<8;i++) { + CHECK_EQ(bus.read(bus.ctx,store_address(i),4,&value),ES_OK);CHECK_EQ(value,stores[i]); + CHECK_EQ(bus.write(bus.ctx,store_address(i),4,~stores[i]),ES_OK); + CHECK_EQ(bus.read(bus.ctx,store_address(i),4,&value),ES_OK);CHECK_EQ(value,~stores[i]); + CHECK_EQ(bus.read(bus.ctx,store_address(i),1,&value),ES_EINVAL); + CHECK_EQ(bus.write(bus.ctx,store_address(i)+1,4,0),ES_EINVAL); + } + value=0xdeadbeef;CHECK_EQ(bus.read(bus.ctx,0x3ff48038,4,&value),ES_EUNSUPPORTED);CHECK_EQ(value,0xdeadbeef); + for(i=0;i<0x200;i+=4) { + uint32_t address=0x3ff48000u+i; + if(i==0x34 || (i>=0x4c && i<=0x58) || (i>=0xb0 && i<=0xbc)) continue; + value=0xdeadbeef;CHECK_EQ(bus.read(bus.ctx,address,4,&value),ES_EUNSUPPORTED);CHECK_EQ(value,0xdeadbeefu); + CHECK_EQ(bus.write(bus.ctx,address,4,0),ES_EUNSUPPORTED); + } + CHECK_EQ(bus.fetch(bus.ctx,0x3ff48034,3,&value),ES_EUNSUPPORTED); + CHECK_EQ(bus.compare_exchange(bus.ctx,0x3ff48034,0,0,&value),ES_EUNSUPPORTED); + CHECK_EQ(esk_esp32_machine_rtc_configure(m,0,NULL,0),ES_OK); + CHECK_EQ(bus.read(bus.ctx,store_address(0),4,&value),ES_EUNSUPPORTED); + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +static void cause_vector_and_retention_on_app_reset(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_xtensa_memory bus[2];esk_xtensa_windows *app,*pro;uint32_t stores[8],value;unsigned flags,i; + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK); + for(i=0;i<8;i++) stores[i]=0x12340000u+i; + CHECK_EQ(esk_esp32_machine_rtc_configure(m,0x3041,stores,8),ES_OK);stores[0]=0; + bus[0]=esk_esp32_machine_bus(m,0);bus[1]=esk_esp32_machine_bus(m,1); + CHECK_EQ(esk_esp32_machine_app_reset_configure(m,1),ES_OK);app=esk_esp32_machine_core(m,1);pro=esk_esp32_machine_core(m,0); + pro->cpu.a[3]=0x98765432; + for(flags=0;flags<4;flags++) { + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff48034,4,flags<<12),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff0002c,4,0),ES_OK); + CHECK_EQ(bus[0].write(bus[0].ctx,0x3ff0002c,4,1),ES_OK); + CHECK_EQ(app->cpu.pc,(flags&1u) ? 0x40000400u : 0x50000000u); + CHECK_EQ(bus[1].read(bus[1].ctx,0x3ff48034,4,&value),ES_OK);CHECK_EQ(value,(flags<<12)|(14u<<6)|1u); + CHECK_EQ(pro->cpu.a[3],0x98765432); + for(i=0;i<8;i++) { CHECK_EQ(bus[1].read(bus[1].ctx,store_address(i),4,&value),ES_OK);CHECK_EQ(value,0x12340000u+i); } + } + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +static void fetched_reset_commit(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_xtensa_memory bus;esk_xtensa_windows *w;uint32_t stores[8]={0},value=0; + const unsigned char code[]={0x32,0x62,0};unsigned event;esk_xtensa_fault fault; + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK); + CHECK_EQ(esk_esp32_machine_rtc_configure(m,0x3041,stores,8),ES_OK); + CHECK_EQ(esk_esp32_machine_app_reset_configure(m,1),ES_OK);bus=esk_esp32_machine_bus(m,0); + CHECK_EQ(bus.write(bus.ctx,0x3ff0002c,4,0),ES_OK);CHECK_EQ(bus.write(bus.ctx,0x3ff00030,4,1),ES_OK); + CHECK_EQ(esk_esp32_machine_configure(m,0,0x40080000),ES_OK); + CHECK_EQ(esk_esp32_fabric_load(esk_esp32_machine_fabric(m),0,0x40080000,code,sizeof code),ES_OK); + w=esk_esp32_machine_core(m,0);w->cpu.a[2]=0x3ff0002c;w->cpu.a[3]=1; + CHECK_EQ(esk_esp32_machine_run_core(m,0,1),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK);CHECK_EQ(event,0);CHECK_EQ(w->cpu.pc,0x40080003); + CHECK_EQ(bus.read(bus.ctx,0x3ff48034,4,&value),ES_OK);CHECK_EQ((value>>6)&63u,14); + CHECK_EQ(esk_esp32_machine_retired(m,0),1); + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +TEST_MAIN_BEGIN + (void)t_str_eq;RUN(reset_snapshot_and_fields);RUN(cause_vector_and_retention_on_app_reset);RUN(fetched_reset_commit); +TEST_MAIN_END diff --git a/tests/vendor/README.md b/tests/vendor/README.md index e0f302036..74b3a80da 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -228,3 +228,9 @@ The [cycle-capture project](xtensa_integer/cycle_probe/README.md) builds with ESP-IDF v4.4.7/actual ESP32 GCC and emits 3,672 framed IRAM kernel observations. A complete QEMU control-flow/protocol run and analyzer rejection tests pass; this is not physical measurement or a calibrated native instruction-time model. + +`xtensa_integer/run_rtc.py` checks pinned reset/retention fields and calls an +unchanged external PRO-ROM reset-reason helper: 1,281 comparisons, 256 calls and +1,792 instructions. Explicit snapshots are required; complete RTC/power domains +and native boot remain unqualified. See the +[RTC contract](../../docs/engineering/esp32-rtc-reset-retention.md). diff --git a/tests/vendor/xtensa_integer/evidence/boot-with-rtc-2026-11-05.json b/tests/vendor/xtensa_integer/evidence/boot-with-rtc-2026-11-05.json new file mode 100644 index 000000000..61fdd7685 --- /dev/null +++ b/tests/vendor/xtensa_integer/evidence/boot-with-rtc-2026-11-05.json @@ -0,0 +1,92 @@ +{ + "boot_verified": false, + "outcome": "blocked", + "core": "PRO only", + "rom_sha256": "9115bbcd03da9f54d3baca5ad94d588a43da54ed332347137ba3dc9010df258b", + "flash_sha256": "52fcca3c31ed835d45e22826d42dd7a7db0c867171fa330f9c56b0a81df7bee7", + "reset_vector": "0x40000400", + "memctl_profile": "bit 0 plus deterministic 256-byte loop-buffer fixture; not physical fetch/timing qualification", + "region_profile": "original ESP32 eight ITLB/DTLB identity regions, reset attribute 2, one ring, no Xtensa cache", + "prid_fixture": "0x0000cdcd", + "prid_reference": "ESP-IDF v4.4.7 xtensa_context.h documents PRO=0xcdcd, APP=0xabab; not a hardware readout", + "rom_tail_policy": "zero-fill absent DROM tail to match QEMU region setup, not silicon contents", + "flash_profile": "immutable SPI1 fixture, not a physical flash identity", + "rtc_fixture": "power-on cause 1 per core, vector selects 1, eight explicit zero retention words; not physical-owner RTC state", + "efuse_fixture_sha256": "2d8192ea7bdf3510bc117b4ea4b833a94b2ff2ceff252ce1610c297ed633d5bb", + "efuse_completion_fixture": "one external completion opportunity every 64 instruction attempts; not physical timing", + "status": -1, + "event": 0, + "pc": "0x4000770a", + "opcode": "0x258", + "address": "0x3ff48088", + "fault_kind": 5, + "attempts": 6699, + "region_readback": [ + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ], + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ] + ], + "region_access_attributes": [ + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ], + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ] + ], + "efuse_completions": 3, + "retired_before_stop": 6698, + "trace_tail": [ + "0x400076fe", + "0x400081d4", + "0x400081d7", + "0x400081da", + "0x400081dc", + "0x400081df", + "0x400081e1", + "0x400081e4", + "0x40007701", + "0x40007704", + "0x40007707", + "0x4000770a" + ], + "next_blocker": "RTC_CNTL_ANA_CONF_REG read at 0x3ff48088, PC 0x4000770a; native SDK boot unverified", + "fixture_note": "Explicit power-on snapshot causes/vector selects and all-zero retention words, not owner RTC state or automatic physical power-on defaults", + "sources_sha256": { + "core/mcu/esp32/machine.c": "2791c0c6ad839ec9c120c1beefccdd427f9add8775643fbcf7bfdb48440f79b4", + "core/mcu/esp32/machine.h": "02c5d94eef8f49de2f3f9e5652483f1e4109f8af6daf8d264a89ba90e3adc3b0", + "core/mcu/xtensa/windows.c": "0d642d048a310250f5954f9a4ae41b6a1e2cb0888ed1afdcf20a9165dc39a0e9", + "tests/vendor/xtensa_integer/probe_rom_boot.py": "4ab35f601e9f416f8b7adeb37207af5edf72d6fcf3f51209098dc92269e0847c" + } +} diff --git a/tests/vendor/xtensa_integer/probe_rom_boot.py b/tests/vendor/xtensa_integer/probe_rom_boot.py index d9126c344..43e9bfe8e 100644 --- a/tests/vendor/xtensa_integer/probe_rom_boot.py +++ b/tests/vendor/xtensa_integer/probe_rom_boot.py @@ -28,6 +28,7 @@ def main(): ap.add_argument('--rom',type=Path,required=True);ap.add_argument('--flash',type=Path,required=True) ap.add_argument('--efuse',type=Path,help='explicit JSON list of 31 raw None-coded fuse words; not inferred board defaults') ap.add_argument('--efuse-completion-period',type=int,default=64,help='external fixture event every N instruction attempts; not physical timing') + ap.add_argument('--rtc-power-on-fixture',action='store_true',help='explicit synthetic power-on reasons/vector selects and eight zero retention words; not owner RTC state') ap.add_argument('--prid',type=lambda value:int(value,0),default=0xcdcd,help='PRO PRID; SDK documents 0xcdcd; 0 reproduces historical invalid-ID fixture') ap.add_argument('--report',type=Path);ap.add_argument('--steps',type=int,default=100000) ap.add_argument('--cc',default='cc');args=ap.parse_args() @@ -57,6 +58,7 @@ def bind(name,result,*params): complete=bind('esk_esp32_fabric_efuse_read_complete',C.c_int,pointer) spi=bind('esk_esp32_fabric_spi1_configure',C.c_int,pointer,C.c_int) core=bind('esk_esp32_machine_core',C.POINTER(Windows),pointer,C.c_uint) + rtc=bind('esk_esp32_machine_rtc_configure',C.c_int,pointer,C.c_uint32,C.POINTER(C.c_uint32),C.c_size_t) memctl=bind('esk_xtensa_memctl_configure',C.c_int,C.POINTER(Windows)) region=bind('esk_xtensa_region_configure',C.c_int,C.POINTER(Windows)) reset=bind('esk_xtensa_lx6_reset',C.c_int,C.POINTER(Windows),C.c_uint) @@ -74,6 +76,9 @@ def bind(name,result,*params): 'flash_profile':'immutable SPI1 fixture, not a physical flash identity'} try: ram=fabric(machine) + if args.rtc_power_on_fixture: + words=(C.c_uint32*8)();assert rtc(machine,0x3041,words,8)==0 + report['rtc_fixture']='power-on cause 1 per core, vector selects 1, eight explicit zero retention words; not physical-owner RTC state' if args.efuse: words=json.loads(args.efuse.read_text(encoding='utf-8')) if not isinstance(words,list) or len(words)!=31 or any(type(n) is not int or not 0<=n<=0xffffffff for n in words):ap.error('eFuse image must contain 31 uint32 words') diff --git a/tests/vendor/xtensa_integer/run_rtc.py b/tests/vendor/xtensa_integer/run_rtc.py new file mode 100644 index 000000000..8141401a8 --- /dev/null +++ b/tests/vendor/xtensa_integer/run_rtc.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""Pinned RTC reset/retention referee and unchanged external ROM helper. + +Explicit raw snapshots, not owner RTC/power state or complete reset-domain boot. +No ROM bytes or vendor implementation are distributed by this runner. +""" +import argparse +import ctypes as C +import hashlib +from pathlib import Path +import random +import re +import subprocess +import tempfile +from run_execute import ROOT,Memory,Fault +from run_windows_c import Windows +from run_machine_c import Allocator +from run_matrix import read_header + + +def main(): + ap=argparse.ArgumentParser(description=__doc__) + for name in ['rtc-reg','rtc-header','rom-symbols','rom']:ap.add_argument('--'+name,type=Path,required=True) + ap.add_argument('--cc',default='cc');args=ap.parse_args();rng=random.Random(0x3041) + reg=read_header(args.rtc_reg,'c2d17880e08b28288fba95c3daa48f9a8672722fd56a5616728ee025dc61e0fb') + header=read_header(args.rtc_header,'90bb6c9a0d497a264041c0ac7abdd0475375edcae812868a9deaed0044994f44') + symbols=read_header(args.rom_symbols,'0160cb9e14e83de87ad7363bd4977a9dd353b22e868a9bd4474e167ee86e58ec') + rom=args.rom.read_bytes();assert hashlib.sha256(rom).hexdigest()=='9115bbcd03da9f54d3baca5ad94d588a43da54ed332347137ba3dc9010df258b' + entry=int(re.search(r'PROVIDE\s*\(\s*rtc_get_reset_reason\s*=\s*(0x[0-9a-f]+)',symbols)[1],16) + def address(name):return 0x3ff48000+int(re.search(r'#define\s+RTC_CNTL_'+name+r'_REG\s+\(DR_REG_RTCCNTL_BASE\s*\+\s*(0x[0-9a-f]+)\)',reg)[1],16) + def field(name): + shift=int(re.search(r'#define\s+RTC_CNTL_'+name+r'_S\s+(\d+)',reg)[1]) + mask=int(re.search(r'#define\s+RTC_CNTL_'+name+r'_V\s+(\S+)',reg)[1],0);return mask,shift + pro_mask,pro_shift=field('RESET_CAUSE_PROCPU');app_mask,app_shift=field('RESET_CAUSE_APPCPU') + vector_mask=sum(mask<>shift)&mask;calls+=1 + assert app_reset(m,1)==0 + assert bus.read(bus.ctx,reset_reg,4,C.byref(value))==0 and ((value.value>>app_shift)&app_mask)==reason + comparisons+=1 + finally:destroy(m) + assert allocator.live_bytes==0 + print(f'{comparisons} pinned RTC field/retention/reset comparisons; {calls} unchanged external ROM rtc_get_reset_reason calls, {instructions} retired instructions on native buses') + + +if __name__=='__main__':main() From a1888efeca7200c20cd2258989969583fbf61fe6 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 18:37:38 +0000 Subject: [PATCH 35/57] Implement ESP32 software GPIO and observable pad hold --- CHANGELOG.md | 18 +- core/mcu/esp32/gpio.c | 139 +++++++++++++++ core/mcu/esp32/gpio.h | 35 ++++ core/mcu/esp32/machine.c | 14 +- core/mcu/esp32/machine.h | 8 + docs/STATUS.md | 9 +- docs/engineering/esp32-boot-verification.md | 14 +- docs/engineering/esp32-dual-core.md | 5 +- docs/engineering/esp32-gpio-pad-hold.md | 141 +++++++++++++++ docs/engineering/esp32-memctl-loop-buffer.md | 2 +- docs/engineering/esp32-rtc-reset-retention.md | 6 +- tests/unit/test_esp32_gpio.c | 110 ++++++++++++ tests/vendor/README.md | 6 + .../evidence/boot-with-gpio-2026-11-05.json | 94 ++++++++++ .../evidence/boot-with-rtc-2026-11-05.json | 5 +- tests/vendor/xtensa_integer/probe_rom_boot.py | 7 +- tests/vendor/xtensa_integer/run_atomctl.py | 2 +- tests/vendor/xtensa_integer/run_controls.py | 2 +- tests/vendor/xtensa_integer/run_efuse.py | 2 +- tests/vendor/xtensa_integer/run_flash_c.py | 2 +- tests/vendor/xtensa_integer/run_gpio.py | 162 ++++++++++++++++++ tests/vendor/xtensa_integer/run_ipi_c.py | 2 +- tests/vendor/xtensa_integer/run_machine_c.py | 2 +- tests/vendor/xtensa_integer/run_memctl.py | 2 +- tests/vendor/xtensa_integer/run_regions.py | 2 +- tests/vendor/xtensa_integer/run_reset_c.py | 2 +- tests/vendor/xtensa_integer/run_rtc.py | 2 +- tests/vendor/xtensa_integer/run_spi_c.py | 2 +- tests/vendor/xtensa_integer/run_tls_c.py | 2 +- 29 files changed, 774 insertions(+), 25 deletions(-) create mode 100644 core/mcu/esp32/gpio.c create mode 100644 core/mcu/esp32/gpio.h create mode 100644 docs/engineering/esp32-gpio-pad-hold.md create mode 100644 tests/unit/test_esp32_gpio.c create mode 100644 tests/vendor/xtensa_integer/evidence/boot-with-gpio-2026-11-05.json create mode 100644 tests/vendor/xtensa_integer/run_gpio.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 98b1432b3..c02d0e08f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Original ESP32 software GPIO and global pad hold + +- Added explicit software-route GPIO fixtures, shared two-bank latches and + set/clear aliases, input-only protection, open-drain/HiZ outputs and externally + resolved inputs. Unresolved enabled inputs fail instead of becoming zero. +- Added global pad hold/auto-hold/clear with captured output controls and live + inputs. Non-pad isolation/power changes remain unsupported, as do complete + IO_MUX/matrix/IRQ/electrical behavior and physical board bindings. +- Corrected the former ANA_CONF label: 0x3ff48088 is DIG_ISO in the pinned SDK. + Earlier observed fault addresses/counts remain unchanged and annotated. +- 3,313 native assertions, 2,048 independent latch/pad comparisons and 216 + interactive compiled ESP32 C calls with nine unchanged SDK helpers pass. + With the additional explicit active GPIO fixture, native ROM reaches 6,734 + instructions and UART0 INT_CLR write at 0x3ff40010; full SDK boot is unverified. + + ### Original ESP32 RTC reset state and retention - Added an explicit RTC reset-state/eight-word retention snapshot profile. @@ -14,7 +30,7 @@ versions follow semantic versioning. There is no release yet. - 53,787 native assertions and 1,281 pinned field/retention/reset comparisons pass; the unchanged external PRO-ROM reset-reason helper passes 256 calls. - With an additional explicit synthetic power-on RTC snapshot, native ROM - reaches 6,698 instructions and blocks on analog configuration read 0x3ff48088. + reaches 6,698 instructions and blocks on digital isolation control read 0x3ff48088. Unknown RTC controls, global/power/watchdog/wakeup behavior, native SDK boot and physical instruction timing remain unqualified. diff --git a/core/mcu/esp32/gpio.c b/core/mcu/esp32/gpio.c new file mode 100644 index 000000000..535ed621d --- /dev/null +++ b/core/mcu/esp32/gpio.c @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* ESP32 TRM v5.9 GPIO hold / RTC DIG_ISO; pinned ESP-IDF v4.4.7 definitions. */ +#include "mcu/esp32/gpio.h" +#include +static uint32_t valid(unsigned bank) { return bank ? 255u : UINT32_C(0x0effffff); } +static uint32_t outputs(unsigned bank) { return bank ? 3u : valid(0); } +static void settle_hold(esk_esp32_gpio *g) +{ + uint32_t hold=(!(g->iso&0x4000u) && ((g->iso&0x8000u) || g->auto_hold)) ? 1u : 0u; + if(hold && !g->held) { + memcpy(g->hold_out,g->out,sizeof g->out);memcpy(g->hold_oe,g->oe,sizeof g->oe);memcpy(g->hold_od,g->od,sizeof g->od); + } + g->held=hold; +} +void esk_esp32_gpio_init(esk_esp32_gpio *g) +{ + if(!g) return; + memset(g,0,sizeof *g);g->iso=UINT32_C(0xaaaa5000); +} +es_status esk_esp32_gpio_configure(esk_esp32_gpio *g,const uint32_t *config,size_t count) +{ + esk_esp32_gpio next;unsigned bank; + if(!g || (!config && count)) return ES_EINVAL; + if(!config && !count) { esk_esp32_gpio_init(g);return ES_OK; } + if(count!=14) return ES_EINVAL; + esk_esp32_gpio_init(&next); + for(bank=0;bank<2;bank++) { + uint32_t route=config[bank]; + if(route&~valid(bank)) return ES_EINVAL; + if((config[2+bank]|config[4+bank]|config[6+bank]|config[8+bank])&~route) return ES_EINVAL; + if((config[4+bank]|config[6+bank])&~outputs(bank)) return ES_EUNSUPPORTED; + next.route[bank]=route;next.ie[bank]=config[2+bank];next.pu[bank]=config[4+bank];next.pd[bank]=config[6+bank];next.od[bank]=config[8+bank]; + if(bank && ((config[10+bank]|config[12+bank])&~UINT32_C(255))) return ES_EINVAL; + next.out[bank]=config[10+bank];next.oe[bank]=config[12+bank]; + } + next.enabled=1;*g=next;return ES_OK; +} +static int selected(const esk_esp32_gpio *g,unsigned pin) +{ + return pin<40 && (g->route[pin/32]&(UINT32_C(1)<<(pin%32))); +} +es_status esk_esp32_gpio_input(esk_esp32_gpio *g,unsigned pin,int known,int high) +{ + uint32_t bit;unsigned bank; + if(!g || pin>=40 || (known!=0 && known!=1) || (high!=0 && high!=1)) return ES_EINVAL; + if(!g->enabled || !selected(g,pin)) return ES_EUNSUPPORTED; + bank=pin/32;bit=UINT32_C(1)<<(pin%32); + if(known) g->known[bank]|=bit;else g->known[bank]&=~bit; + if(high) g->level[bank]|=bit;else g->level[bank]&=~bit; + return ES_OK; +} +es_status esk_esp32_gpio_output(const esk_esp32_gpio *g,unsigned pin,esk_esp32_gpio_pad *pad) +{ + unsigned bank;uint32_t bit,out,oe,od;esk_esp32_gpio_pad result; + if(!g || !pad || pin>=40) return ES_EINVAL; + if(!g->enabled || !selected(g,pin)) return ES_EUNSUPPORTED; + bank=pin/32;bit=UINT32_C(1)<<(pin%32); + out=(g->held ? g->hold_out[bank] : g->out[bank])&bit; + oe=(g->held ? g->hold_oe[bank] : g->oe[bank])&outputs(bank)&bit; + od=(g->held ? g->hold_od[bank] : g->od[bank])&bit; + result.drive=oe && !(od && out) ? (out ? 1 : 0) : -1; + result.input_enabled=(g->ie[bank]&bit) ? 1u : 0u;result.pull_up=(g->pu[bank]&bit) ? 1u : 0u;result.pull_down=(g->pd[bank]&bit) ? 1u : 0u; + *pad=result;return ES_OK; +} +es_status esk_esp32_gpio_auto_hold_event(esk_esp32_gpio *g) +{ + if(!g) return ES_EINVAL; + if(!g->enabled) return ES_EUNSUPPORTED; + if(g->iso&0x800u) g->auto_hold=1; + settle_hold(g);return ES_OK; +} +static es_status decode(const esk_esp32_gpio *g,uint32_t address,unsigned bytes,uint32_t *offset) +{ + if(!g->enabled) return ES_EUNSUPPORTED; + if(address>=0x3ff44000 && address<0x3ff45000) *offset=address-0x3ff44000; + else if(address>=0x3ff48088 && address<0x3ff4808c) *offset=0x1000; + else return ES_EUNSUPPORTED; + if(bytes!=4 || (address&3u)) return ES_EINVAL; + return ES_OK; +} +es_status esk_esp32_gpio_read(const esk_esp32_gpio *g,uint32_t address,unsigned bytes,uint32_t *value) +{ + uint32_t off;es_status st;unsigned pin,bank; + if(!g || !value) return ES_EINVAL; + st=decode(g,address,bytes,&off);if(st!=ES_OK) return st; + if(off==0x1000) { *value=g->iso|(g->auto_hold<<9);return ES_OK; } + if(off>=0x530 && off<=0x5cc) { + pin=(off-0x530)/4;if(!selected(g,pin)) return ES_EUNSUPPORTED; + *value=256;return ES_OK; + } + if(off>=0x88 && off<=0x124) { + pin=(off-0x88)/4;if(!selected(g,pin)) return ES_EUNSUPPORTED; + *value=((g->od[pin/32]>>(pin%32))&1u)<<2;return ES_OK; + } + switch(off) { + case 4:*value=g->out[0];break;case 0x10:*value=g->out[1];break; + case 0x20:*value=g->oe[0];break;case 0x2c:*value=g->oe[1];break; + case 0x3c:case 0x40: + bank=off==0x40 ? 1u : 0u; + if(g->ie[bank]&~g->known[bank]) return ES_EUNSUPPORTED; + *value=g->level[bank]&g->ie[bank];break; + default:return ES_EUNSUPPORTED; + } + return ES_OK; +} +es_status esk_esp32_gpio_write(esk_esp32_gpio *g,uint32_t address,unsigned bytes,uint32_t value) +{ + uint32_t off,*reg,mask;unsigned bank,operation,pin;es_status st; + if(!g) return ES_EINVAL; + st=decode(g,address,bytes,&off);if(st!=ES_OK) return st; + if(off==0x1000) { + /* Do not accept memory/Wi-Fi/power/pad-isolation changes without their + * domain model. Reserved writes/RO status have no effect. */ + if((value^g->iso)&UINT32_C(0xffff3180)) return ES_EUNSUPPORTED; + if((value&0xc000u)==0xc000u) return ES_EUNSUPPORTED; + g->iso=(g->iso&UINT32_C(0xffff3180))|(value&0xc800u); + if(value&0x400u) g->auto_hold=0; + settle_hold(g);return ES_OK; + } + if(off>=0x530 && off<=0x5cc) { + pin=(off-0x530)/4;if(!selected(g,pin) || value!=256) return ES_EUNSUPPORTED; + return ES_OK; /* already selected noninverted software-GPIO fixture */ + } + if(off>=0x88 && off<=0x124) { + pin=(off-0x88)/4;if(!selected(g,pin)) return ES_EUNSUPPORTED; + if(value&~UINT32_C(4)) return ES_EUNSUPPORTED; + mask=UINT32_C(1)<<(pin%32);bank=pin/32; + if(value&4u) g->od[bank]|=mask;else g->od[bank]&=~mask; + return ES_OK; + } + if(off>=4 && off<=0x18) { bank=off>=0x10 ? 1u : 0u;operation=(off-(bank ? 0x10u : 4u))/4;reg=&g->out[bank]; } + else if(off>=0x20 && off<=0x34) { bank=off>=0x2c ? 1u : 0u;operation=(off-(bank ? 0x2cu : 0x20u))/4;reg=&g->oe[bank]; } + else if(off==0x3c || off==0x40) return ES_OK; /* input registers are RO */ + else return ES_EUNSUPPORTED; + value&=bank ? 255u : UINT32_MAX; + if(operation==0) *reg=value;else if(operation==1) *reg|=value;else *reg&=~value; + return ES_OK; +} diff --git a/core/mcu/esp32/gpio.h b/core/mcu/esp32/gpio.h new file mode 100644 index 000000000..99980e626 --- /dev/null +++ b/core/mcu/esp32/gpio.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_ESP32_GPIO_H +#define ESK_ESP32_GPIO_H +#include "embsim/types.h" +/* Opt-in original-ESP32 software-GPIO route fixture, not full GPIO/IO_MUX. + * Config: route[2], input-enable[2], pull-up[2], pull-down[2], PAD_DRIVER[2], OUT[2], ENABLE[2] (14 words). + * Routes are explicit normal/noninverted software GPIO selection, not guessed + * chip reset/pinmux or package/board exposure. Initial latches/directions are explicit; + * unsupported PIN fields are disabled fixture choices, not chip reset facts. + * OUT_SEL reads 256; only this unchanged fixed selection can be written. GPIO_PIN fields other than + * PAD_DRIVER are fixed disabled in this profile; enabling them fails. + * OUT/ENABLE are shared hardware latches; input-only 34..39 never drive. + * Inputs are supplied resolved levels, not fabricated floating levels. + * Output = drive0/drive1/HiZ plus pull controls, not voltage/current/IV timing. + * DIG_ISO: global pad hold/unhold, auto-hold enable/latch/clear only. Isolation, + * sleep/power/Wi-Fi/memory changes fail; fixed fields retain published defaults. + * Auto-hold event is an external latch input, not SoC sleep execution. + * No IO_MUX/peripheral matrix, per-pad hold, IRQ/wakeup/filter/straps, sleep + * configuration, package mapping, loading/glitches/strength/timing qualification. + * All calls serialized; failed operations preserve state/output. */ +typedef struct esk_esp32_gpio { + uint32_t enabled,route[2],ie[2],pu[2],pd[2],out[2],oe[2],od[2],known[2],level[2]; + uint32_t hold_out[2],hold_oe[2],hold_od[2],iso,auto_hold,held; +} esk_esp32_gpio; +/* drive -1=HiZ, 0=low, 1=high. Pulls describe controls to an external solver. */ +typedef struct esk_esp32_gpio_pad { int drive;unsigned input_enabled,pull_up,pull_down; } esk_esp32_gpio_pad; +void esk_esp32_gpio_init(esk_esp32_gpio *g); +es_status esk_esp32_gpio_configure(esk_esp32_gpio *g,const uint32_t *config,size_t count); +es_status esk_esp32_gpio_read(const esk_esp32_gpio *g,uint32_t address,unsigned bytes,uint32_t *value); +es_status esk_esp32_gpio_write(esk_esp32_gpio *g,uint32_t address,unsigned bytes,uint32_t value); +es_status esk_esp32_gpio_input(esk_esp32_gpio *g,unsigned pin,int known,int high); +es_status esk_esp32_gpio_output(const esk_esp32_gpio *g,unsigned pin,esk_esp32_gpio_pad *pad); +es_status esk_esp32_gpio_auto_hold_event(esk_esp32_gpio *g); +#endif diff --git a/core/mcu/esp32/machine.c b/core/mcu/esp32/machine.c index bcc0403e6..26fa2e036 100644 --- a/core/mcu/esp32/machine.c +++ b/core/mcu/esp32/machine.c @@ -9,6 +9,7 @@ struct esk_esp32_machine { uint32_t app_clock,app_stall,app_boot,app_reset,app_reset_enabled,app_reset_vector,app_reset_pending; unsigned spi1_owned,efuse_owned; uint32_t rtc_enabled,rtc_state,rtc_store[8]; + esk_esp32_gpio gpio; esk_esp32_matrix matrix;struct machine_view view[2]; }; /* Never mutate an executing CPU from a bus callback. Synchronize the @@ -101,6 +102,7 @@ static es_status bus_read(void *ctx,uint32_t address,unsigned bytes,uint32_t *va { struct machine_view *v=(struct machine_view *)ctx;esk_xtensa_memory ram;es_status st; st=control_read(v->m,address,bytes,value);if(st!=ES_EUNSUPPORTED) return st; + st=esk_esp32_gpio_read(&v->m->gpio,address,bytes,value);if(st!=ES_EUNSUPPORTED) return st; st=esk_esp32_matrix_read(&v->m->matrix,address,bytes,value); if(st!=ES_EUNSUPPORTED) return st; ram=esk_esp32_fabric_bus(v->m->fabric,v->cpu);return ram.read(ram.ctx,address,bytes,value); @@ -113,6 +115,7 @@ static es_status bus_write(void *ctx,uint32_t address,unsigned bytes,uint32_t va if(st==ES_OK && !v->m->executing) { apply_reset(v->m);sync_inputs(v->m); } return st; } + st=esk_esp32_gpio_write(&v->m->gpio,address,bytes,value);if(st!=ES_EUNSUPPORTED) return st; st=esk_esp32_matrix_write(&v->m->matrix,address,bytes,value); if(st!=ES_EUNSUPPORTED) { if(st==ES_OK && !v->m->executing) sync_inputs(v->m); @@ -194,7 +197,7 @@ es_status esk_esp32_machine_create(esk_alloc *a,esk_esp32_machine **out) m=(esk_esp32_machine *)esk_calloc(a,1,sizeof *m);if(!m) return ES_ENOMEM; m->a=a;st=esk_esp32_fabric_create(a,&m->fabric); if(st!=ES_OK) { esk_free(a,m,sizeof *m);return st; } - esk_esp32_matrix_init(&m->matrix); + esk_esp32_matrix_init(&m->matrix);esk_esp32_gpio_init(&m->gpio); for(i=0;i<2;i++) { esk_xtensa_windows_init(&m->cpu[i],0,NULL);m->view[i].m=m;m->view[i].cpu=i; } *out=m;return ES_OK; } @@ -245,3 +248,12 @@ es_status esk_esp32_machine_round(esk_esp32_machine *m,unsigned events[2],unsign return ES_OK; } uint64_t esk_esp32_machine_retired(const esk_esp32_machine *m,unsigned cpu) { return m && cpu<2 ? m->retired[cpu] : 0; } + +es_status esk_esp32_machine_gpio_configure(esk_esp32_machine *m,const uint32_t *config,size_t count) +{ return !m || m->executing ? ES_EINVAL : esk_esp32_gpio_configure(&m->gpio,config,count); } +es_status esk_esp32_machine_gpio_input(esk_esp32_machine *m,unsigned pin,int known,int high) +{ return !m || m->executing ? ES_EINVAL : esk_esp32_gpio_input(&m->gpio,pin,known,high); } +es_status esk_esp32_machine_gpio_output(const esk_esp32_machine *m,unsigned pin,esk_esp32_gpio_pad *pad) +{ return !m || m->executing ? ES_EINVAL : esk_esp32_gpio_output(&m->gpio,pin,pad); } +es_status esk_esp32_machine_gpio_auto_hold_event(esk_esp32_machine *m) +{ return !m || m->executing ? ES_EINVAL : esk_esp32_gpio_auto_hold_event(&m->gpio); } diff --git a/core/mcu/esp32/machine.h b/core/mcu/esp32/machine.h index 7c011f2b1..1d6940aa4 100644 --- a/core/mcu/esp32/machine.h +++ b/core/mcu/esp32/machine.h @@ -4,6 +4,7 @@ #define ESK_ESP32_MACHINE_H #include "mcu/esp32/fabric.h" #include "mcu/esp32/matrix.h" +#include "mcu/esp32/gpio.h" #include "mcu/xtensa/windows.h" typedef struct esk_esp32_machine esk_esp32_machine; /* Experimental two-context execution harness over shared internal memory. @@ -27,6 +28,13 @@ esk_esp32_fabric *esk_esp32_machine_fabric(esk_esp32_machine *m); * not irq_line on CPU peripheral input numbers. Internal timer/software and * profiling inputs remain CPU-local. No API is re-entrant or thread-safe. */ esk_xtensa_memory esk_esp32_machine_bus(esk_esp32_machine *m,unsigned cpu); +/* Explicit software-GPIO route fixture; see gpio.h for config ordering and + * electrical/pinmux/IRQ limitations. No board pad defaults are inferred. + * Mutating inputs/querying projected pads is permitted only between turns. */ +es_status esk_esp32_machine_gpio_configure(esk_esp32_machine *m,const uint32_t *config,size_t count); +es_status esk_esp32_machine_gpio_input(esk_esp32_machine *m,unsigned pin,int known,int high); +es_status esk_esp32_machine_gpio_output(const esk_esp32_machine *m,unsigned pin,esk_esp32_gpio_pad *pad); +es_status esk_esp32_machine_gpio_auto_hold_event(esk_esp32_machine *m); /* Opt-in SPI1 owns matrix source 29; the eFuse profile owns source 44. * Both ownerships are active only while their respective profiles are enabled. * completion/enable changes route after commit (immediately for host bus writes). diff --git a/docs/STATUS.md b/docs/STATUS.md index a225befd6..3e162b4d4 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -7,13 +7,20 @@ does not mean the described behaviour exists. ## Original ESP32 dual-core target investigation (2026-11-04) +**Latest GPIO update:** the [software-GPIO/pad-hold profile](engineering/esp32-gpio-pad-hold.md) +now supports explicit routes, live resolved inputs, projected drive/HiZ and hold +capture/clear. With all explicit probe fixtures it reaches 6,734 instructions, +then UART0 INT_CLR write at 0x3ff40010/PC 0x4000900b. Full GPIO/IO_MUX, electrical +loading, board binding, power isolation and native SDK boot remain incomplete. +The earlier 0x3ff48088 blocker was DIG_ISO; its former analog label is corrected. + **Current implementation update:** an [explicit eFuse read/reload profile](engineering/esp32-efuse-read-profile.md) now supports None-coded input images, read protection and completion routing. With a supplied synthetic unprogrammed image, real ROM execution advances from seven to 6,692 retired instructions without an RTC snapshot. With the new [RTC reset/retention profile](engineering/esp32-rtc-reset-retention.md) and an explicit synthetic power-on snapshot, it reaches 6,698 instructions, then blocks -on analog configuration read at 0x3ff48088, PC 0x4000770a. [MEMCTL](engineering/esp32-memctl-loop-buffer.md) now +on digital isolation control read at 0x3ff48088, PC 0x4000770a. [MEMCTL](engineering/esp32-memctl-loop-buffer.md) now has reset/control and a bounded functional loop-buffer profile. Its physical fill/fetch/pipeline timing remains unqualified. [Region protection](engineering/esp32-region-protection.md) now implements fixed diff --git a/docs/engineering/esp32-boot-verification.md b/docs/engineering/esp32-boot-verification.md index 0b9d712ff..00910d01d 100644 --- a/docs/engineering/esp32-boot-verification.md +++ b/docs/engineering/esp32-boot-verification.md @@ -3,8 +3,8 @@ **Result on 2026-11-05: the owner's board reports ESP32-D0WD-V3 revision 3.1, JEDEC C4 60 16 and 4 MB flash. Giantec's GT25Q32A-U datasheet matches that ID; exact package/ordering suffix remains unconfirmed. The pinned SDK example boots -in Espressif QEMU. Native EmbSIM stops at an RTC analog-configuration read after 6,698 retired -instructions with explicit synthetic eFuse and RTC snapshots; without an eFuse profile, +in Espressif QEMU. Native EmbSIM reaches UART0 initialization after 6,734 retired +instructions with explicit synthetic eFuse/RTC and active GPIO fixtures; without an eFuse profile, the baseline stops at an eFuse read after seven.** Neither result establishes successful native boot. @@ -114,10 +114,16 @@ configuration or printed SDK capacity must not substitute for hardware identity. ## Native EmbSIM result: blocked -**Latest follow-up:** [RTC reset-state/retention](esp32-rtc-reset-retention.md) +**Latest follow-up:** [Software GPIO/global hold](esp32-gpio-pad-hold.md) now +handles the pad-hold portion of DIG_ISO with an explicit routing fixture. The +probe reaches 6,734 retirements, blocked at UART0 INT_CLR write 0x3ff40010, +PC 0x4000900b. The earlier 0x3ff48088 label was wrong: it is DIG_ISO, not ANA_CONF. +The active fixture does not qualify full isolation, physical GPIO or board boot. + +**Prior RTC-only result:** [RTC reset-state/retention](esp32-rtc-reset-retention.md) now supplies protected causes/vector latches and retained words from an explicit snapshot. With the synthetic power-on fixture, the probe reaches 6,698 retirements -and RTC_CNTL_ANA_CONF_REG read 0x3ff48088/PC 0x4000770a. Native boot remains blocked; +and RTC_CNTL_DIG_ISO_REG read 0x3ff48088/PC 0x4000770a. Native boot remains blocked; the snapshot is not a capture of owner RTC state. **Prior MEMCTL-only result:** [Functional MEMCTL/loop buffering](esp32-memctl-loop-buffer.md) diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 263a398b1..a6716725b 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -36,7 +36,10 @@ it reached 545 instructions in the prior profile. [Functional MEMCTL/loop buffering](esp32-memctl-loop-buffer.md) now advances it to 6,692 instructions without an RTC snapshot. Adding the [explicit RTC reset/retention fixture](esp32-rtc-reset-retention.md) reaches 6,698 -and the next analog-configuration read at 0x3ff48088/PC 0x4000770a. +and the next digital-isolation-control read at 0x3ff48088/PC 0x4000770a. +[Software GPIO and pad hold](esp32-gpio-pad-hold.md) adds another explicit fixture +and advances the probe to UART0 INT_CLR after 6,734 instructions. Full GPIO/IO_MUX, +board wiring and electrical loading remain unqualified. [Instruction timing](esp32-instruction-timing.md) still requires physical evidence; a buildable capture firmware is prepared without claiming calibrated latency. It is not the owner's fuse dump and does not establish full boot. The [Giantec storage subset](esp32-giantec-profile.md) diff --git a/docs/engineering/esp32-gpio-pad-hold.md b/docs/engineering/esp32-gpio-pad-hold.md new file mode 100644 index 000000000..adc624a59 --- /dev/null +++ b/docs/engineering/esp32-gpio-pad-hold.md @@ -0,0 +1,141 @@ +# Original ESP32 software GPIO and global pad hold + +**Implemented: explicit software-GPIO routing fixtures, native shared latches, +resolved digital inputs, push-pull/open-drain/HiZ output projection and the global +pad-hold subset of DIG_ISO. This is not complete GPIO/IO_MUX, physical pad loading, +power isolation, package/board integration or SDK boot.** + +## Register identity correction + +The earlier RTC evidence mislabeled 0x3ff48088 as ANA_CONF. The pinned ESP-IDF +header identifies it as **RTC_CNTL_DIG_ISO_REG**. Historical fault PC/address and +retirement counts are unchanged; its descriptive label is corrected and annotated. +The observed ROM path clears global pad force-hold and pulses auto-hold clear. +This implementation does not infer analog/power behavior from that address. + +## Explicit routing and initial values + +`esk_esp32_machine_gpio_configure` supplies **14 words**, in pairs of low/high +banks: route, input-enable, pull-up, pull-down, PAD_DRIVER, OUT, ENABLE. Each route +means an already selected, noninverted software-GPIO path through the otherwise +unimplemented IO_MUX/matrix. All initial latches/directions are supplied; other +PIN fields are fixed disabled choices in this fixture, not asserted chip reset +values. GPIO register headers mark many reset values undefined. + +The logical GPIO mask follows the pinned SoC capability header. GPIO24/28..31 are +excluded; GPIO34..39 are input-only with no internal pulls. GPIO20 is a logical +slot in that SDK mask; this does not assert it is bonded/exposed on the owner's +package or 30-pin board. Routes are never inferred from a product photo or +package marking. Unrouted endpoints return unsupported, not a fabricated HiZ +physical pad. Detach/reconfigure are host fixture operations, not silicon reset. + +## GPIO behavior + +- OUT/OUT1 and ENABLE/ENABLE1 have ordinary writes and W1TS/W1TC aliases. Banks + mask to 32/8 bits. Shared latches are visible to both serialized CPU contexts. + Input-only pins never drive, even if firmware sets their direction bits. +- Selected GPIO_PIN registers expose PAD_DRIVER bit 2 in the fixed disabled- + interrupt/wakeup profile. Other feature requests fail unchanged. OUT_SEL + readback is the fixed software signal 256; writing the same selection is + supported, while peripheral/inverted/changed selections fail. +- Output queries return **drive-low, drive-high or HiZ**, plus input-enable and + pull controls. Open-drain high releases the output. Pull controls describe + inputs to an external circuit solver; the model does not turn them into + guessed voltage/current or drive strength. +- `gpio_input` supplies a resolved known/unknown digital level. IN/IN1 use the + configured input-enable mask; disabled buffers read zero, as documented by + the SDK helper. If any enabled input in the requested bank is unresolved, + the read fails unchanged instead of assuming a floating level is ground. + Outputs do not silently override external resolution or mask contention. +- GPIO W1 aliases are WO in the TRM and their reads remain unsupported. Some + SDK high-bank write helpers read a WO alias through a bitfield macro first; + those executions are not qualified. Native high-bank writes/latches and + high-bank input reads are separately checked. + +MMIO is aligned 32-bit only. Unknown GPIO registers, IO_MUX changes, matrix +peripheral routes, per-pad hold, IRQ/wakeup/filter/strap/sleep configuration and +pad IV/voltage/glitch/timing behavior remain unsupported. This profile is an +unregistered experimental backend, not a graphical board-pin binding. + +## Global pad hold at RTC DIG_ISO + +Only global FORCE_HOLD, FORCE_UNHOLD, AUTOHOLD_EN, latched AUTOHOLD status and +write-one auto-hold clear are implemented. Other isolation fields retain their +published default bits. Any request to change memory, ROM, Wi-Fi, digital-wrap, +pad isolation or undocumented force controls fails unchanged; no memory-bank +map, power transition or floating-bus result is guessed. + +When hold becomes effective, current output level, enable and open-drain controls +are captured. Subsequent register writes remain readable but do not alter the +projected held output. Release exposes current controls. FORCE_UNHOLD disables +hold; simultaneous FORCE_HOLD/FORCE_UNHOLD is explicitly unsupported because its +priority is unqualified. Capture is global across selected endpoints. + +The auto-hold event is an explicit external latch input and only sets the latch +when armed. It is **not** a complete SoC Deep-sleep transition: the CPU, clocks, +power and wakeup path are not put to sleep by this API. Disarming does not silently +clear the latch; the dedicated clear pulse does. Clear reads zero and status is +read-only. Inputs remain resolved live levels while output controls are held, +consistent with the SDK's documented input behavior. Fixed input/pull route +configuration cannot be changed through unsupported IO_MUX operations. + +All host input/output/event calls occur between instruction turns, including +while the core is marked running. Native fetched stores apply their bus effects +in the existing serialized functional contract. CPU-only reset preserves shared +GPIO/hold state; digital-core/global power reset and sleep retention are not +qualified by that observation. No physical instruction/bus duration is inferred. + +## Evidence + +- `test_esp32_gpio`: **3,313 assertions** covering all selected logical pins, + direction/level/open-drain combinations, input-only outputs, bank widths, + W1 aliases, resolved/unresolved/disabled inputs, explicit copied initial + latches, fixed routing, failed-operation preservation, hold/release/latch + clear, fetched stores and CPU-only reset preservation. +- `run_gpio.py`: **2,048 independent latch/output comparisons**, with register + addresses and valid/output masks parsed from pinned vendor inputs. +- Actual ESP32 GCC executes **216 interactive calls / 25,380 instructions** + through native buses and **nine unchanged SDK helper bodies**. Both CPU + contexts use low-bank outputs (avoiding the unqualified WO alias reads), + and GPIO34 as a resolved input. Between fetched instructions the host changes + the input and supplies an auto-hold latch event; firmware observes the changed + input and modifies/disables output while the projected pad remains captured. + After the clear pulse, the output is HiZ. There are no Python memory callbacks, + host PC handoffs or claims of executing a full Deep-sleep/SDK application. + +With the additional `--gpio-active-fixture` (no routed board pads, explicitly +zero initial OUT/ENABLE, defined active DIG_ISO controls), the external ROM probe +reaches **6,734 retired instructions**. It then stops at **UART0 INT_CLR** write +0x3ff40010, PC 0x4000900b, opcode 0x08a9. The +[recorded probe](../../tests/vendor/xtensa_integer/evidence/boot-with-gpio-2026-11-05.json) +retains all earlier ROM/eFuse/RTC fixture limitations. No fallback MMIO is added; +UART, full board boot and calibrated instruction timing remain unverified. + +## References and reproduction + +ESP32 TRM v5.9 GPIO pin hold and RTC DIG_ISO; ESP-IDF v4.4.7 inputs: + +| Input | SHA-256 | +|---|---| +| gpio_reg.h | 87609e6eb2349cfaa569344e3ee0fb6c38b9a1dbb23b81af32e701c53af1cf84 | +| gpio_ll.h | e1c839b9fe34ce525af1c51935a8b0409aade38452516daadf2f3f62062a2907 | +| gpio_struct.h | d7a6a542b8a0aa6e9530ca3a9201aefd4f12b195169cc8613a191bda2ed6cfc8 | +| misc.h | 552e2d4ce5be7d93d228eb3b48bd959d0485290a7476ce5a7de438fe7ab9e152 | +| soc_caps.h | 8f353538e6b94a0dac7097b41247f65c5429c3f4c7a0be3481a5e9c9fa71668d | +| gpio_sig_map.h | 40d374f73e03922ac13f4a9d7b6fa780f54e5afde7178ac5369bef4a41cc7c49 | +| driver/gpio.h | 0cd2f0ad1aaaba56e452943ff1c8022d07d00b707b93b368d5539fa759a193a2 | + +RTC header pin and other target references are in the [RTC contract](esp32-rtc-reset-retention.md). +Unchanged test headers/helpers retain their vendor notices in temporary files; +no vendor implementation is compiled into the runtime. + +```sh +python3 tests/vendor/xtensa_integer/run_gpio.py \ + --gpio-reg /path/to/gpio_reg.h --gpio-ll /path/to/gpio_ll.h \ + --gpio-struct /path/to/gpio_struct.h --rtc-reg /path/to/rtc_cntl_reg.h \ + --misc /path/to/hal/misc.h --caps /path/to/soc_caps.h \ + --sig-map /path/to/gpio_sig_map.h \ + --compiler /path/to/xtensa-esp32-elf-gcc \ + --objcopy /path/to/xtensa-esp32-elf-objcopy \ + --linker /path/to/xtensa-esp32-elf-ld --nm /path/to/xtensa-esp32-elf-nm +``` diff --git a/docs/engineering/esp32-memctl-loop-buffer.md b/docs/engineering/esp32-memctl-loop-buffer.md index 18df5aa28..06dac4d37 100644 --- a/docs/engineering/esp32-memctl-loop-buffer.md +++ b/docs/engineering/esp32-memctl-loop-buffer.md @@ -63,7 +63,7 @@ values and five loop counts pass: **20 calls, 20 compiled hardware loops and is explicit. No CPU timing is inferred from retirement or provider fetch counts. The subsequent [RTC reset/retention profile](esp32-rtc-reset-retention.md), with -an additional explicit synthetic snapshot, now advances to analog configuration +an additional explicit synthetic snapshot, now advances to digital isolation control read after 6,698 instructions. The historical MEMCTL-only result below retains its original source/fixture scope. diff --git a/docs/engineering/esp32-rtc-reset-retention.md b/docs/engineering/esp32-rtc-reset-retention.md index 6f22da483..2f6eaeeb8 100644 --- a/docs/engineering/esp32-rtc-reset-retention.md +++ b/docs/engineering/esp32-rtc-reset-retention.md @@ -74,11 +74,15 @@ ABI call setup is explicit. This exercises the supplied PRO-ROM routine; it is not APP-specific ROM qualification, complete SDK boot or a physical RTC capture. No ROM bytes are embedded in the runner/application/repository. +The subsequent [GPIO/pad-hold profile](esp32-gpio-pad-hold.md) now advances the +probe further, to UART initialization after 6,734 instructions. Historical RTC-only +evidence below retains its source/fixture scope; its DIG_ISO label is corrected. + With `--rtc-power-on-fixture`, the diagnostic explicitly supplies reason 1 for each core, both vector selects 1 and eight zero retention words. These are chosen snapshot inputs, not measured owner state or automatic defaults. The same external ROM/SDK image now retires **6,698 instructions**, then fails at -**RTC_CNTL_ANA_CONF_REG** read 0x3ff48088, PC 0x4000770a, opcode 0x0258. The +**RTC_CNTL_DIG_ISO_REG** read 0x3ff48088, PC 0x4000770a, opcode 0x0258. The [recorded result](../../tests/vendor/xtensa_integer/evidence/boot-with-rtc-2026-11-05.json) remains blocked. Without that additional fixture, the existing 6,692-instruction reset-state-read failure remains valid. No unknown-MMIO fallback or PC shortcut diff --git a/tests/unit/test_esp32_gpio.c b/tests/unit/test_esp32_gpio.c new file mode 100644 index 000000000..9801dab21 --- /dev/null +++ b/tests/unit/test_esp32_gpio.c @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/gpio.h" +#include "mcu/esp32/machine.h" +#define GPIO_BASE UINT32_C(0x3ff44000) +#define DIG_ISO UINT32_C(0x3ff48088) +static void fixture_config(uint32_t config[14]) +{ + memset(config,0,14*sizeof *config);config[0]=0x0effffff;config[1]=255;config[2]=config[0];config[3]=255; +} +static void bank_latches_and_projection(void) +{ + esk_esp32_gpio g;esk_esp32_gpio_pad pad;uint32_t config[14],value;unsigned pin,oe,high,od,bank; + fixture_config(config);esk_esp32_gpio_init(&g);CHECK_EQ(esk_esp32_gpio_configure(&g,config,14),ES_OK); + for(pin=0;pin<40;pin++) { + uint32_t bit=UINT32_C(1)<<(pin%32);bank=pin/32; + if(!(config[bank]&bit)) continue; + for(oe=0;oe<2;oe++) for(high=0;high<2;high++) for(od=0;od<2;od++) { + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+(bank ? 0x10u : 4u),4,high ? bit : 0u),ES_OK); + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+(bank ? 0x2cu : 0x20u),4,oe ? bit : 0u),ES_OK); + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+0x88u+pin*4u,4,od<<2),ES_OK); + CHECK_EQ(esk_esp32_gpio_output(&g,pin,&pad),ES_OK); + CHECK_EQ(pad.drive,oe && pin<34 && !(od && high) ? (high ? 1 : 0) : -1); + CHECK_EQ(esk_esp32_gpio_read(&g,GPIO_BASE+(bank ? 0x10u : 4u),4,&value),ES_OK);CHECK_EQ(value,high ? bit : 0u); + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+(bank ? 0x14u : 8u),4,bit),ES_OK); + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+(bank ? 0x18u : 0xcu),4,bit),ES_OK); + CHECK_EQ(esk_esp32_gpio_read(&g,GPIO_BASE+(bank ? 0x10u : 4u),4,&value),ES_OK);CHECK_EQ(value,0); + } + } + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+0x10,4,UINT32_MAX),ES_OK); + CHECK_EQ(esk_esp32_gpio_read(&g,GPIO_BASE+0x10,4,&value),ES_OK);CHECK_EQ(value,255); +} +static void resolved_inputs_and_unchanged_failures(void) +{ + esk_esp32_gpio g,before;esk_esp32_gpio_pad pad;uint32_t config[14]={0},value=0xdeadbeef; + config[0]=(1u<<2)|(1u<<4);config[2]=config[0];config[4]=1u<<2;config[6]=1u<<4; + esk_esp32_gpio_init(&g);CHECK_EQ(esk_esp32_gpio_configure(&g,config,14),ES_OK); + CHECK_EQ(esk_esp32_gpio_read(&g,GPIO_BASE+0x3c,4,&value),ES_EUNSUPPORTED);CHECK_EQ(value,0xdeadbeefu); + CHECK_EQ(esk_esp32_gpio_input(&g,2,1,1),ES_OK);CHECK_EQ(esk_esp32_gpio_input(&g,4,1,0),ES_OK); + CHECK_EQ(esk_esp32_gpio_read(&g,GPIO_BASE+0x3c,4,&value),ES_OK);CHECK_EQ(value,1u<<2); + CHECK_EQ(esk_esp32_gpio_output(&g,2,&pad),ES_OK);CHECK_EQ(pad.pull_up,1);CHECK_EQ(pad.drive,-1); + CHECK_EQ(esk_esp32_gpio_input(&g,2,0,0),ES_OK);before=g; + CHECK_EQ(esk_esp32_gpio_write(&g,DIG_ISO,4,g.iso^(1u<<18)),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&g,&before,sizeof g),0); + CHECK_EQ(esk_esp32_gpio_write(&g,DIG_ISO,4,g.iso|0xc000u),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&g,&before,sizeof g),0); + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+0x90,4,0x80),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&g,&before,sizeof g),0); + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+4,1,1),ES_EINVAL);CHECK_EQ(memcmp(&g,&before,sizeof g),0); + CHECK_EQ(esk_esp32_gpio_read(&g,GPIO_BASE+8,4,&value),ES_EUNSUPPORTED); + CHECK_EQ(esk_esp32_gpio_output(&g,0,&pad),ES_EUNSUPPORTED); + config[1]=1u<<2;config[5]=1u<<2;CHECK_EQ(esk_esp32_gpio_configure(&g,config,14),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&g,&before,sizeof g),0); +} +static void hold_release_and_auto_latch(void) +{ + esk_esp32_gpio g;esk_esp32_gpio_pad pad;uint32_t config[14]={0},value;unsigned cycle; + config[0]=1u<<2;esk_esp32_gpio_init(&g);CHECK_EQ(esk_esp32_gpio_configure(&g,config,14),ES_OK); + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+0x20,4,1u<<2),ES_OK); + for(cycle=0;cycle<16;cycle++) { + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+4,4,0),ES_OK); + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+0x90,4,0),ES_OK); + CHECK_EQ(esk_esp32_gpio_write(&g,DIG_ISO,4,(g.iso&~UINT32_C(0x4000))|0x8000u),ES_OK); + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+4,4,1u<<2),ES_OK); + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+0x90,4,4),ES_OK); + CHECK_EQ(esk_esp32_gpio_output(&g,2,&pad),ES_OK);CHECK_EQ(pad.drive,0); + CHECK_EQ(esk_esp32_gpio_write(&g,DIG_ISO,4,(g.iso&~UINT32_C(0x8000))|0x4000u),ES_OK); + CHECK_EQ(esk_esp32_gpio_output(&g,2,&pad),ES_OK);CHECK_EQ(pad.drive,-1); /* high open drain */ + } + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+0x90,4,0),ES_OK); + CHECK_EQ(esk_esp32_gpio_write(&g,DIG_ISO,4,(g.iso&~UINT32_C(0x4000))|0x800u),ES_OK); + CHECK_EQ(esk_esp32_gpio_auto_hold_event(&g),ES_OK); + { esk_esp32_gpio before=g; + CHECK_EQ(esk_esp32_gpio_write(&g,DIG_ISO,4,(g.iso^UINT32_C(0x40000))|0x400u),ES_EUNSUPPORTED); + CHECK_EQ(memcmp(&g,&before,sizeof g),0); } + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+4,4,0),ES_OK); + CHECK_EQ(esk_esp32_gpio_output(&g,2,&pad),ES_OK);CHECK_EQ(pad.drive,1); + CHECK_EQ(esk_esp32_gpio_read(&g,DIG_ISO,4,&value),ES_OK);CHECK(value&0x200u);CHECK(!(value&0x400u)); + CHECK_EQ(esk_esp32_gpio_write(&g,DIG_ISO,4,value|0x400u),ES_OK); + CHECK_EQ(esk_esp32_gpio_output(&g,2,&pad),ES_OK);CHECK_EQ(pad.drive,0); + CHECK_EQ(esk_esp32_gpio_read(&g,DIG_ISO,4,&value),ES_OK);CHECK(!(value&0x600u)); +} +static void fetched_gpio_and_cpu_reset(void) +{ + esk_alloc a;esk_esp32_machine *m=NULL;esk_xtensa_memory bus;esk_xtensa_windows *w;esk_esp32_gpio_pad pad; + uint32_t config[14]={0};unsigned event;esk_xtensa_fault fault;const unsigned char code[]={0x32,0x62,0}; + config[0]=1u<<2;esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_machine_create(&a,&m),ES_OK); + CHECK_EQ(esk_esp32_machine_gpio_configure(m,config,14),ES_OK);bus=esk_esp32_machine_bus(m,0); + CHECK_EQ(bus.write(bus.ctx,GPIO_BASE+0x20,4,1u<<2),ES_OK); + CHECK_EQ(esk_esp32_machine_configure(m,0,0x40080000),ES_OK);w=esk_esp32_machine_core(m,0);w->cpu.a[2]=GPIO_BASE+4;w->cpu.a[3]=1u<<2; + CHECK_EQ(esk_esp32_fabric_load(esk_esp32_machine_fabric(m),0,0x40080000,code,sizeof code),ES_OK); + CHECK_EQ(esk_esp32_machine_run_core(m,0,1),ES_OK);CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK); + CHECK_EQ(event,0);CHECK_EQ(esk_esp32_machine_gpio_output(m,2,&pad),ES_OK);CHECK_EQ(pad.drive,1); + CHECK_EQ(esk_esp32_machine_app_reset_configure(m,1),ES_OK); + CHECK_EQ(esk_esp32_machine_gpio_output(m,2,&pad),ES_OK);CHECK_EQ(pad.drive,1); + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +static void explicit_snapshots_and_fixed_route(void) +{ + esk_esp32_gpio g,before;esk_esp32_gpio_pad pad;uint32_t config[14]={0},value=0; + config[0]=1u<<2;config[10]=1u<<2;config[12]=1u<<2; + esk_esp32_gpio_init(&g);CHECK_EQ(esk_esp32_gpio_configure(&g,config,14),ES_OK);config[10]=0; + CHECK_EQ(esk_esp32_gpio_output(&g,2,&pad),ES_OK);CHECK_EQ(pad.drive,1); + CHECK_EQ(esk_esp32_gpio_read(&g,GPIO_BASE+0x3c,4,&value),ES_OK);CHECK_EQ(value,0); /* input buffer disabled */ + CHECK_EQ(esk_esp32_gpio_read(&g,GPIO_BASE+0x538,4,&value),ES_OK);CHECK_EQ(value,256); + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+0x538,4,256),ES_OK);before=g; + CHECK_EQ(esk_esp32_gpio_write(&g,GPIO_BASE+0x538,4,257),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&g,&before,sizeof g),0); + config[11]=256;CHECK_EQ(esk_esp32_gpio_configure(&g,config,14),ES_EINVAL);CHECK_EQ(memcmp(&g,&before,sizeof g),0); + CHECK_EQ(esk_esp32_gpio_configure(&g,config,13),ES_EINVAL);CHECK_EQ(memcmp(&g,&before,sizeof g),0); +} +TEST_MAIN_BEGIN + (void)t_str_eq;RUN(bank_latches_and_projection);RUN(resolved_inputs_and_unchanged_failures);RUN(hold_release_and_auto_latch);RUN(fetched_gpio_and_cpu_reset);RUN(explicit_snapshots_and_fixed_route); +TEST_MAIN_END diff --git a/tests/vendor/README.md b/tests/vendor/README.md index 74b3a80da..1ca02084f 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -234,3 +234,9 @@ unchanged external PRO-ROM reset-reason helper: 1,281 comparisons, 256 calls and 1,792 instructions. Explicit snapshots are required; complete RTC/power domains and native boot remain unqualified. See the [RTC contract](../../docs/engineering/esp32-rtc-reset-retention.md). + +`xtensa_integer/run_gpio.py` checks 2,048 independent GPIO latch/pad cases and +216 interactive C calls/25,380 instructions with nine unchanged SDK helpers. +Input changes and held-output checks use native buses and explicit route/latch +fixtures, not a complete IO_MUX/electrical/Deep-sleep/board model. See the +[GPIO/pad-hold contract](../../docs/engineering/esp32-gpio-pad-hold.md). diff --git a/tests/vendor/xtensa_integer/evidence/boot-with-gpio-2026-11-05.json b/tests/vendor/xtensa_integer/evidence/boot-with-gpio-2026-11-05.json new file mode 100644 index 000000000..9c236fc23 --- /dev/null +++ b/tests/vendor/xtensa_integer/evidence/boot-with-gpio-2026-11-05.json @@ -0,0 +1,94 @@ +{ + "boot_verified": false, + "outcome": "blocked", + "core": "PRO only", + "rom_sha256": "9115bbcd03da9f54d3baca5ad94d588a43da54ed332347137ba3dc9010df258b", + "flash_sha256": "52fcca3c31ed835d45e22826d42dd7a7db0c867171fa330f9c56b0a81df7bee7", + "reset_vector": "0x40000400", + "memctl_profile": "bit 0 plus deterministic 256-byte loop-buffer fixture; not physical fetch/timing qualification", + "region_profile": "original ESP32 eight ITLB/DTLB identity regions, reset attribute 2, one ring, no Xtensa cache", + "prid_fixture": "0x0000cdcd", + "prid_reference": "ESP-IDF v4.4.7 xtensa_context.h documents PRO=0xcdcd, APP=0xabab; not a hardware readout", + "rom_tail_policy": "zero-fill absent DROM tail to match QEMU region setup, not silicon contents", + "flash_profile": "immutable SPI1 fixture, not a physical flash identity", + "gpio_fixture": "no routed pads; defined active-domain DIG_ISO defaults and hold controls only, no isolation/power emulation", + "rtc_fixture": "power-on cause 1 per core, vector selects 1, eight explicit zero retention words; not physical-owner RTC state", + "efuse_fixture_sha256": "2d8192ea7bdf3510bc117b4ea4b833a94b2ff2ceff252ce1610c297ed633d5bb", + "efuse_completion_fixture": "one external completion opportunity every 64 instruction attempts; not physical timing", + "status": -1, + "event": 0, + "pc": "0x4000900b", + "opcode": "0x8a9", + "address": "0x3ff40010", + "fault_kind": 6, + "attempts": 6735, + "region_readback": [ + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ], + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ] + ], + "region_access_attributes": [ + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ], + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ] + ], + "efuse_completions": 3, + "retired_before_stop": 6734, + "trace_tail": [ + "0x40008ff0", + "0x40008ff2", + "0x40008ff4", + "0x40008ff6", + "0x40008ff8", + "0x40008ffb", + "0x40008ffd", + "0x40008fff", + "0x40009002", + "0x40009005", + "0x40009008", + "0x4000900b" + ], + "next_blocker": "UART0 UART_INT_CLR_REG write at 0x3ff40010, PC 0x4000900b; native SDK boot remains unverified", + "fixture_note": "Explicit software GPIO/DIG_ISO active fixture with no routed pads and zero initial latch/direction values, not a board GPIO/reset/electrical capture", + "sources_sha256": { + "core/mcu/esp32/gpio.c": "64db32412e6b1319ccc6b2d745cb9edfe1d40f7614fdf309fb25ce2cdd2a0a3e", + "core/mcu/esp32/gpio.h": "ce8dd5492d70991e3675a99f752bf0d969c0df0be0885f0c2944e6c6f4a213be", + "core/mcu/esp32/machine.c": "628b3fc0c484e79f89e85465b373ace2629fcc96c919b0e2c7a61adccbf6d90d", + "core/mcu/esp32/machine.h": "66386860bef50cc98adbb9dd5597359bdb66f41d56bf877fba9e76a38100098c", + "tests/vendor/xtensa_integer/probe_rom_boot.py": "3e75f0ec86a349b2b3f84628474d152018cf88cac7c4855de3254ee9b7f89719" + } +} diff --git a/tests/vendor/xtensa_integer/evidence/boot-with-rtc-2026-11-05.json b/tests/vendor/xtensa_integer/evidence/boot-with-rtc-2026-11-05.json index 61fdd7685..ade766fa3 100644 --- a/tests/vendor/xtensa_integer/evidence/boot-with-rtc-2026-11-05.json +++ b/tests/vendor/xtensa_integer/evidence/boot-with-rtc-2026-11-05.json @@ -81,12 +81,13 @@ "0x40007707", "0x4000770a" ], - "next_blocker": "RTC_CNTL_ANA_CONF_REG read at 0x3ff48088, PC 0x4000770a; native SDK boot unverified", + "next_blocker": "RTC_CNTL_DIG_ISO_REG read at 0x3ff48088, PC 0x4000770a; native SDK boot unverified", "fixture_note": "Explicit power-on snapshot causes/vector selects and all-zero retention words, not owner RTC state or automatic physical power-on defaults", "sources_sha256": { "core/mcu/esp32/machine.c": "2791c0c6ad839ec9c120c1beefccdd427f9add8775643fbcf7bfdb48440f79b4", "core/mcu/esp32/machine.h": "02c5d94eef8f49de2f3f9e5652483f1e4109f8af6daf8d264a89ba90e3adc3b0", "core/mcu/xtensa/windows.c": "0d642d048a310250f5954f9a4ae41b6a1e2cb0888ed1afdcf20a9165dc39a0e9", "tests/vendor/xtensa_integer/probe_rom_boot.py": "4ab35f601e9f416f8b7adeb37207af5edf72d6fcf3f51209098dc92269e0847c" - } + }, + "register_label_correction": "0x3ff48088 is DIG_ISO per pinned rtc_cntl_reg.h; prior descriptive ANA_CONF label was wrong. Observed fault/address/PC/count are unchanged." } diff --git a/tests/vendor/xtensa_integer/probe_rom_boot.py b/tests/vendor/xtensa_integer/probe_rom_boot.py index 43e9bfe8e..66c83d0ee 100644 --- a/tests/vendor/xtensa_integer/probe_rom_boot.py +++ b/tests/vendor/xtensa_integer/probe_rom_boot.py @@ -28,6 +28,7 @@ def main(): ap.add_argument('--rom',type=Path,required=True);ap.add_argument('--flash',type=Path,required=True) ap.add_argument('--efuse',type=Path,help='explicit JSON list of 31 raw None-coded fuse words; not inferred board defaults') ap.add_argument('--efuse-completion-period',type=int,default=64,help='external fixture event every N instruction attempts; not physical timing') + ap.add_argument('--gpio-active-fixture',action='store_true',help='explicit no-routed-pads software-GPIO/DIG_ISO hold fixture; isolation/power transitions unsupported') ap.add_argument('--rtc-power-on-fixture',action='store_true',help='explicit synthetic power-on reasons/vector selects and eight zero retention words; not owner RTC state') ap.add_argument('--prid',type=lambda value:int(value,0),default=0xcdcd,help='PRO PRID; SDK documents 0xcdcd; 0 reproduces historical invalid-ID fixture') ap.add_argument('--report',type=Path);ap.add_argument('--steps',type=int,default=100000) @@ -41,7 +42,7 @@ def main(): with tempfile.TemporaryDirectory(prefix='embsim-native-rom-probe-') as tmp: lib=Path(tmp)/'probe.so' sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c', 'efuse.c','machine.c','matrix.c','flashmap.c','flashspi.c','w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c', 'efuse.c','machine.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT/'core/util/alloc.c'] subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC', '-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) @@ -58,6 +59,7 @@ def bind(name,result,*params): complete=bind('esk_esp32_fabric_efuse_read_complete',C.c_int,pointer) spi=bind('esk_esp32_fabric_spi1_configure',C.c_int,pointer,C.c_int) core=bind('esk_esp32_machine_core',C.POINTER(Windows),pointer,C.c_uint) + gpio=bind('esk_esp32_machine_gpio_configure',C.c_int,pointer,C.POINTER(C.c_uint32),C.c_size_t) rtc=bind('esk_esp32_machine_rtc_configure',C.c_int,pointer,C.c_uint32,C.POINTER(C.c_uint32),C.c_size_t) memctl=bind('esk_xtensa_memctl_configure',C.c_int,C.POINTER(Windows)) region=bind('esk_xtensa_region_configure',C.c_int,C.POINTER(Windows)) @@ -76,6 +78,9 @@ def bind(name,result,*params): 'flash_profile':'immutable SPI1 fixture, not a physical flash identity'} try: ram=fabric(machine) + if args.gpio_active_fixture: + config=(C.c_uint32*14)();assert gpio(machine,config,14)==0 + report['gpio_fixture']='no routed pads; defined active-domain DIG_ISO defaults and hold controls only, no isolation/power emulation' if args.rtc_power_on_fixture: words=(C.c_uint32*8)();assert rtc(machine,0x3041,words,8)==0 report['rtc_fixture']='power-on cause 1 per core, vector selects 1, eight explicit zero retention words; not physical-owner RTC state' diff --git a/tests/vendor/xtensa_integer/run_atomctl.py b/tests/vendor/xtensa_integer/run_atomctl.py index 01c6a1e3f..501a3b380 100644 --- a/tests/vendor/xtensa_integer/run_atomctl.py +++ b/tests/vendor/xtensa_integer/run_atomctl.py @@ -28,7 +28,7 @@ def main(): subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(obj),str(binary)],check=True) ops=binary.read_bytes();assert len(ops)==len(specs)*3 sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] sources += [ROOT/'core/util/alloc.c'];lib=out/'atomctl.so' subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) api=C.CDLL(str(lib));pointer=C.c_void_p;wp=C.POINTER(Windows) diff --git a/tests/vendor/xtensa_integer/run_controls.py b/tests/vendor/xtensa_integer/run_controls.py index 529ebee70..a7f7f2b64 100644 --- a/tests/vendor/xtensa_integer/run_controls.py +++ b/tests/vendor/xtensa_integer/run_controls.py @@ -38,7 +38,7 @@ def main(): with tempfile.TemporaryDirectory(prefix='embsim-controls-') as tmp: lib = Path(tmp) / 'controls.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_efuse.py b/tests/vendor/xtensa_integer/run_efuse.py index a50c32b41..a5eeebf25 100644 --- a/tests/vendor/xtensa_integer/run_efuse.py +++ b/tests/vendor/xtensa_integer/run_efuse.py @@ -63,7 +63,7 @@ def get(value,name):mask,shift=field(name);return (value>>shift)&mask with tempfile.TemporaryDirectory(prefix='embsim-efuse-ref-') as tmp: out=Path(tmp);lib=out/'efuse.so' sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] sources += [ROOT/'core/util/alloc.c'] subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) api=C.CDLL(str(lib));pointer=C.c_void_p diff --git a/tests/vendor/xtensa_integer/run_flash_c.py b/tests/vendor/xtensa_integer/run_flash_c.py index 5e50fcd79..5fe00192f 100644 --- a/tests/vendor/xtensa_integer/run_flash_c.py +++ b/tests/vendor/xtensa_integer/run_flash_c.py @@ -68,7 +68,7 @@ def compile_code(name, source, base, entry, flags=()): flash_image = bytes(image) lib = out / 'flash.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_gpio.py b/tests/vendor/xtensa_integer/run_gpio.py new file mode 100644 index 000000000..ffe51ebfc --- /dev/null +++ b/tests/vendor/xtensa_integer/run_gpio.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""GPIO/hold oracle and nine unchanged ESP-IDF helpers on the native bus. + +Explicit software routes, not IO_MUX reset/package/electrical/SoC sleep proof. +High-bank W1 aliases are WO in the TRM: SDK routines reading them are unqualified. +""" +import argparse +import ctypes as C +from pathlib import Path +import random +import re +import subprocess +import tempfile +from run_execute import ROOT,Memory,Fault +from run_windows_c import Windows +from run_machine_c import Allocator +from run_matrix import read_header + + +class Pad(C.Structure): + _fields_=[('drive',C.c_int)]+[(n,C.c_uint) for n in ['ie','pu','pd']] + + +def main(): + ap=argparse.ArgumentParser(description=__doc__) + for n in ['gpio-reg','gpio-ll','gpio-struct','rtc-reg','misc','caps','sig-map']:ap.add_argument('--'+n,type=Path,required=True) + for n in ['compiler','objcopy','linker','nm']:ap.add_argument('--'+n,required=True) + ap.add_argument('--cc',default='cc');args=ap.parse_args();rng=random.Random(0xaaaa5000) + reg=read_header(args.gpio_reg,'87609e6eb2349cfaa569344e3ee0fb6c38b9a1dbb23b81af32e701c53af1cf84') + ll=read_header(args.gpio_ll,'e1c839b9fe34ce525af1c51935a8b0409aade38452516daadf2f3f62062a2907') + struct=read_header(args.gpio_struct,'d7a6a542b8a0aa6e9530ca3a9201aefd4f12b195169cc8613a191bda2ed6cfc8') + rtc=read_header(args.rtc_reg,'c2d17880e08b28288fba95c3daa48f9a8672722fd56a5616728ee025dc61e0fb') + misc=read_header(args.misc,'552e2d4ce5be7d93d228eb3b48bd959d0485290a7476ce5a7de438fe7ab9e152') + caps=read_header(args.caps,'8f353538e6b94a0dac7097b41247f65c5429c3f4c7a0be3481a5e9c9fa71668d') + sig=read_header(args.sig_map,'40d374f73e03922ac13f4a9d7b6fa780f54e5afde7178ac5369bef4a41cc7c49') + mask_line=re.search(r'^#define SOC_GPIO_VALID_GPIO_MASK\s+(.+)',caps,re.M)[1] + pins=[n for n in range(40) if n not in list(map(int,re.findall(r'BIT(\d+)',mask_line)))] + out_line=re.search(r'^#define SOC_GPIO_VALID_OUTPUT_GPIO_MASK\s+(.+)',caps,re.M)[1] + outputs=[n for n in pins if n not in list(map(int,re.findall(r'BIT(\d+)',out_line)))] + masks=[sum(1<<(n%32) for n in pins if n//32==b) for b in range(2)] + def address(name):return 0x3ff44000+int(re.search(r'#define\s+GPIO_'+name+r'_REG\s+\(DR_REG_GPIO_BASE\s*\+\s*(0x[0-9a-f]+)\)',reg)[1],16) + helpers=[] + for name in ['output_enable','output_disable','od_enable','od_disable','set_level','get_level','deep_sleep_hold_en','deep_sleep_hold_dis','deep_sleep_hold_is_en']: + match=re.search(r'^static inline [^\n]*\bgpio_ll_'+name+r'\([^\n]*\)\n\{',ll,re.M);assert match,name + end=match.end();depth=1 + while depth: + if ll[end]=='{':depth+=1 + elif ll[end]=='}':depth-=1 + end+=1 + helpers.append(ll[match.start():end]) + with tempfile.TemporaryDirectory(prefix='embsim-gpio-c-') as tmp: + out=Path(tmp);lib=out/'gpio.so' + sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/util/alloc.c'] + subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) + api=C.CDLL(str(lib));pointer=C.c_void_p + def bind(name,result,*params):f=getattr(api,name);f.restype=result;f.argtypes=list(params);return f + ai=bind('esk_alloc_init',None,C.POINTER(Allocator),pointer);create=bind('esk_esp32_machine_create',C.c_int,C.POINTER(Allocator),C.POINTER(pointer));destroy=bind('esk_esp32_machine_destroy',None,pointer) + config=bind('esk_esp32_machine_gpio_configure',C.c_int,pointer,C.POINTER(C.c_uint32),C.c_size_t) + input_level=bind('esk_esp32_machine_gpio_input',C.c_int,pointer,C.c_uint,C.c_int,C.c_int) + output=bind('esk_esp32_machine_gpio_output',C.c_int,pointer,C.c_uint,C.POINTER(Pad));hold=bind('esk_esp32_machine_gpio_auto_hold_event',C.c_int,pointer) + bus_get=bind('esk_esp32_machine_bus',Memory,pointer,C.c_uint);fabric=bind('esk_esp32_machine_fabric',pointer,pointer) + load=bind('esk_esp32_fabric_load',C.c_int,pointer,C.c_uint,C.c_uint32,pointer,C.c_size_t) + configure=bind('esk_esp32_machine_configure',C.c_int,pointer,C.c_uint,C.c_uint32);core=bind('esk_esp32_machine_core',C.POINTER(Windows),pointer,C.c_uint) + run=bind('esk_esp32_machine_run_core',C.c_int,pointer,C.c_uint,C.c_int);step=bind('esk_esp32_machine_step',C.c_int,pointer,C.c_uint,C.POINTER(C.c_uint),C.POINTER(Fault)) + allocator=Allocator();ai(C.byref(allocator),None);m=pointer();assert create(C.byref(allocator),C.byref(m))==0 + comparisons=0;calls=0;instructions=0;value=C.c_uint32();pad=Pad();event=C.c_uint();fault=Fault() + try: + bus=bus_get(m,0);settings=(C.c_uint32*14)(masks[0],masks[1]);assert config(m,settings,14)==0 + expected=[[0,0],[0,0]] + for trial in range(1024): + bank=rng.randrange(2);kind=rng.randrange(2);operation=rng.randrange(3);data=rng.getrandbits(32) + name=('OUT' if kind==0 else 'ENABLE')+('1' if bank else '') + suffix=['','_W1TS','_W1TC'][operation] + assert bus.write(bus.ctx,address(name+suffix),4,data)==0 + data&=255 if bank else 0xffffffff + if operation==0:expected[kind][bank]=data + elif operation==1:expected[kind][bank]|=data + else:expected[kind][bank]&=~data + assert bus.read(bus.ctx,address(name),4,C.byref(value))==0 and value.value==expected[kind][bank];comparisons+=1 + pin=rng.choice(pins);b=pin//32;bit=1<<(pin%32) + assert output(m,pin,C.byref(pad))==0 + wanted=(1 if expected[0][b]&bit else 0) if pin in outputs and expected[1][b]&bit else -1 + assert pad.drive==wanted;comparisons+=1 + (out/'gpio_struct.h').write_text(struct,encoding='utf-8');(out/'misc.h').write_text(misc,encoding='utf-8') + (out/'rtc_cntl_reg.h').write_text(rtc,encoding='utf-8');(out/'gpio_reg.h').write_text(reg,encoding='utf-8');(out/'soc.h').write_text('#include \n#define DR_REG_GPIO_BASE 0x3ff44000\n#define DR_REG_RTCCNTL_BASE 0x3ff48000\n#define BIT(n) (1U<<(n))\n',encoding='utf-8') + source=ll[:ll.index('#pragma once')]+'''\n#include +#include +#include "gpio_struct.h" +#include "misc.h" +#include "rtc_cntl_reg.h" +#include "gpio_reg.h" +typedef int gpio_num_t; +#define SIG_GPIO_OUT_IDX '''+str(int(re.search(r'#define\s+SIG_GPIO_OUT_IDX\s+(\d+)',sig)[1]))+''' +#define REG_READ(r) (*(volatile uint32_t *)(r)) +#define REG_WRITE(r,v) (*(volatile uint32_t *)(r)=(v)) +#define CLEAR_PERI_REG_MASK(r,m) REG_WRITE(r,REG_READ(r)&~(m)) +#define SET_PERI_REG_MASK(r,m) REG_WRITE(r,REG_READ(r)|(m)) +#define GET_PERI_REG_MASK(r,m) (REG_READ(r)&(m)) +'''+ '\n'.join(helpers)+''' +unsigned work(unsigned pin,unsigned flags) { + gpio_dev_t *hw=(gpio_dev_t *)0x3ff44000; + volatile unsigned *phase=(volatile unsigned *)0x3ffb9000; + gpio_ll_output_enable(hw,pin); + if(flags&1) gpio_ll_od_enable(hw,pin);else gpio_ll_od_disable(hw,pin); + gpio_ll_set_level(hw,pin,(flags>>1)&1); + gpio_ll_deep_sleep_hold_en(hw); + *phase=1;while(*phase!=2) { } + unsigned input=gpio_ll_get_level(hw,34); + gpio_ll_set_level(hw,pin,!((flags>>1)&1)); + gpio_ll_output_disable(hw,pin); + *phase=3;while(*phase!=4) { } + unsigned held=gpio_ll_deep_sleep_hold_is_en(hw); + gpio_ll_deep_sleep_hold_dis(hw); + SET_PERI_REG_MASK(RTC_CNTL_DIG_ISO_REG,RTC_CNTL_CLR_DG_PAD_AUTOHOLD); + return input|(held<<1); +} +''' + src=out/'work.c';obj=out/'work.o';elf=out/'work.elf';binary=out/'work.bin';src.write_text(source,encoding='utf-8') + subprocess.run([args.compiler,'-B'+str(Path(args.compiler).parent)+'/','-O1','-ffreestanding','-fno-builtin','-mtext-section-literals','-c',str(src),'-o',str(obj)],check=True) + subprocess.run([args.linker,'--no-relax','-Ttext=0x40080000','--entry=work',str(obj),'-o',str(elf)],check=True) + subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(elf),str(binary)],check=True) + symbols={f[2]:int(f[0],16) for line in subprocess.check_output([args.nm,str(elf)],text=True).splitlines() if len(f:=line.split())==3} + code=binary.read_bytes();assert load(fabric(m),0,0x40080000,C.c_char_p(code),len(code))==0 + assert bus.write(bus.ctx,0x3ff00030,4,1)==0 + # Low-bank outputs avoid SDK high-bank writes which first read WO aliases. + for cpu in range(2): + for pin in [n for n in outputs if n<32]: + for flags in range(4): + bit=1< Date: Thu, 5 Nov 2026 21:38:33 +0000 Subject: [PATCH 36/57] Implement ESP32 UART0 FIFO framing and interrupt profile --- CHANGELOG.md | 15 ++ core/mcu/esp32/machine.c | 47 ++++- core/mcu/esp32/machine.h | 10 + core/mcu/esp32/uart.c | 168 ++++++++++++++++ core/mcu/esp32/uart.h | 46 +++++ docs/STATUS.md | 11 +- docs/engineering/esp32-boot-verification.md | 16 +- docs/engineering/esp32-dual-core.md | 4 + docs/engineering/esp32-uart0.md | 163 +++++++++++++++ tests/unit/test_esp32_uart.c | 186 ++++++++++++++++++ tests/vendor/README.md | 6 + .../evidence/boot-with-uart0-2026-11-05.json | 95 +++++++++ tests/vendor/xtensa_integer/probe_rom_boot.py | 7 +- tests/vendor/xtensa_integer/run_atomctl.py | 2 +- tests/vendor/xtensa_integer/run_controls.py | 2 +- tests/vendor/xtensa_integer/run_efuse.py | 2 +- tests/vendor/xtensa_integer/run_flash_c.py | 2 +- tests/vendor/xtensa_integer/run_gpio.py | 2 +- tests/vendor/xtensa_integer/run_ipi_c.py | 2 +- tests/vendor/xtensa_integer/run_machine_c.py | 2 +- tests/vendor/xtensa_integer/run_memctl.py | 2 +- tests/vendor/xtensa_integer/run_regions.py | 2 +- tests/vendor/xtensa_integer/run_reset_c.py | 2 +- tests/vendor/xtensa_integer/run_rtc.py | 2 +- tests/vendor/xtensa_integer/run_spi_c.py | 2 +- tests/vendor/xtensa_integer/run_tls_c.py | 2 +- tests/vendor/xtensa_integer/run_uart.py | 158 +++++++++++++++ 27 files changed, 937 insertions(+), 21 deletions(-) create mode 100644 core/mcu/esp32/uart.c create mode 100644 core/mcu/esp32/uart.h create mode 100644 docs/engineering/esp32-uart0.md create mode 100644 tests/unit/test_esp32_uart.c create mode 100644 tests/vendor/xtensa_integer/evidence/boot-with-uart0-2026-11-05.json create mode 100644 tests/vendor/xtensa_integer/run_uart.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c02d0e08f..854f7e94a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Original ESP32 normal asynchronous UART0 + +- Added an opt-in fixed-FIFO UART0 profile with APB/AHB firmware access, + source-clock fractional-divider accounting, 5..8 data bits, parity and 1/1.5 + stop bits, projected TX state/bit and framed completion output. +- Added CTS gating, external decoded RX/error inputs, FIFO ordering/overflow, + interrupt enable/W1C/reassertion and shared source-34 routing after commit. +- Kept SDK-documented unreliable RX reset and the two-stop-bit workaround + unsupported, alongside UART1/2, advanced modes and physical pin/clock/USB + integration. This does not qualify CPU timing or complete UART/board behavior. +- 915,199 native assertions, 316,077 independent bit/frame comparisons and + 128 compiled ESP32 C calls with eleven unchanged SDK helpers pass. + Native ROM with an additional explicit UART0 fixture reaches 6,737 + retirements and blocks at UART1 INT_CLR; full SDK boot remains unverified. + ### Original ESP32 software GPIO and global pad hold - Added explicit software-route GPIO fixtures, shared two-bank latches and diff --git a/core/mcu/esp32/machine.c b/core/mcu/esp32/machine.c index 26fa2e036..49e579997 100644 --- a/core/mcu/esp32/machine.c +++ b/core/mcu/esp32/machine.c @@ -7,9 +7,9 @@ struct esk_esp32_machine { esk_alloc *a;esk_esp32_fabric *fabric; esk_xtensa_windows cpu[2];uint64_t retired[2];unsigned running[2],executing; uint32_t app_clock,app_stall,app_boot,app_reset,app_reset_enabled,app_reset_vector,app_reset_pending; - unsigned spi1_owned,efuse_owned; + unsigned spi1_owned,efuse_owned,uart_owned; uint32_t rtc_enabled,rtc_state,rtc_store[8]; - esk_esp32_gpio gpio; + esk_esp32_gpio gpio;esk_esp32_uart uart; esk_esp32_matrix matrix;struct machine_view view[2]; }; /* Never mutate an executing CPU from a bus callback. Synchronize the @@ -29,6 +29,11 @@ static void sync_inputs(esk_esp32_machine *m) } else if(m->efuse_owned) { (void)esk_esp32_matrix_source(&m->matrix,44,0);m->efuse_owned=0; } + if(esk_esp32_uart_irq(&m->uart,&asserted)==ES_OK) { + (void)esk_esp32_matrix_source(&m->matrix,34,asserted);m->uart_owned=1; + } else if(m->uart_owned) { + (void)esk_esp32_matrix_source(&m->matrix,34,0);m->uart_owned=0; + } for(cpu=0;cpu<2;cpu++) if(m->cpu[cpu].irq.enabled && !(cpu==1 && m->app_reset_enabled && m->app_reset)) { uint32_t levels=esk_esp32_matrix_levels(&m->matrix,cpu); for(input=0;input<32;input++) { @@ -102,6 +107,8 @@ static es_status bus_read(void *ctx,uint32_t address,unsigned bytes,uint32_t *va { struct machine_view *v=(struct machine_view *)ctx;esk_xtensa_memory ram;es_status st; st=control_read(v->m,address,bytes,value);if(st!=ES_EUNSUPPORTED) return st; + st=esk_esp32_uart_read(&v->m->uart,address,bytes,value); + if(st!=ES_EUNSUPPORTED) { if(st==ES_OK && !v->m->executing) sync_inputs(v->m);return st; } st=esk_esp32_gpio_read(&v->m->gpio,address,bytes,value);if(st!=ES_EUNSUPPORTED) return st; st=esk_esp32_matrix_read(&v->m->matrix,address,bytes,value); if(st!=ES_EUNSUPPORTED) return st; @@ -115,6 +122,8 @@ static es_status bus_write(void *ctx,uint32_t address,unsigned bytes,uint32_t va if(st==ES_OK && !v->m->executing) { apply_reset(v->m);sync_inputs(v->m); } return st; } + st=esk_esp32_uart_write(&v->m->uart,address,bytes,value); + if(st!=ES_EUNSUPPORTED) { if(st==ES_OK && !v->m->executing) sync_inputs(v->m);return st; } st=esk_esp32_gpio_write(&v->m->gpio,address,bytes,value);if(st!=ES_EUNSUPPORTED) return st; st=esk_esp32_matrix_write(&v->m->matrix,address,bytes,value); if(st!=ES_EUNSUPPORTED) { @@ -157,6 +166,10 @@ es_status esk_esp32_machine_source(esk_esp32_machine *m,unsigned source,int asse if(esk_esp32_fabric_efuse_irq(m->fabric,&asserted_now)==ES_OK) return ES_EUNSUPPORTED; if(m->efuse_owned) sync_inputs(m); } + if(source==34) { + if(esk_esp32_uart_irq(&m->uart,&asserted_now)==ES_OK) return ES_EUNSUPPORTED; + if(m->uart_owned) sync_inputs(m); + } st=esk_esp32_matrix_source(&m->matrix,source,asserted);if(st==ES_OK) sync_inputs(m);return st; } es_status esk_esp32_machine_gpio_irq(esk_esp32_machine *m,unsigned cpu,int nmi,int asserted) @@ -197,7 +210,7 @@ es_status esk_esp32_machine_create(esk_alloc *a,esk_esp32_machine **out) m=(esk_esp32_machine *)esk_calloc(a,1,sizeof *m);if(!m) return ES_ENOMEM; m->a=a;st=esk_esp32_fabric_create(a,&m->fabric); if(st!=ES_OK) { esk_free(a,m,sizeof *m);return st; } - esk_esp32_matrix_init(&m->matrix);esk_esp32_gpio_init(&m->gpio); + esk_esp32_matrix_init(&m->matrix);esk_esp32_gpio_init(&m->gpio);esk_esp32_uart_init(&m->uart); for(i=0;i<2;i++) { esk_xtensa_windows_init(&m->cpu[i],0,NULL);m->view[i].m=m;m->view[i].cpu=i; } *out=m;return ES_OK; } @@ -257,3 +270,31 @@ es_status esk_esp32_machine_gpio_output(const esk_esp32_machine *m,unsigned pin, { return !m || m->executing ? ES_EINVAL : esk_esp32_gpio_output(&m->gpio,pin,pad); } es_status esk_esp32_machine_gpio_auto_hold_event(esk_esp32_machine *m) { return !m || m->executing ? ES_EINVAL : esk_esp32_gpio_auto_hold_event(&m->gpio); } + +es_status esk_esp32_machine_uart_configure(esk_esp32_machine *m,int on,int rxd,int cts,int dsr) +{ + es_status st;if(!m || m->executing) return ES_EINVAL; + st=esk_esp32_uart_configure(&m->uart,on,rxd,cts,dsr);if(st==ES_OK) sync_inputs(m);return st; +} +es_status esk_esp32_machine_uart_inputs(esk_esp32_machine *m,int rxd,int cts,int dsr) +{ + es_status st;if(!m || m->executing) return ES_EINVAL; + st=esk_esp32_uart_inputs(&m->uart,rxd,cts,dsr);if(st==ES_OK) sync_inputs(m);return st; +} +es_status esk_esp32_machine_uart_receive(esk_esp32_machine *m,uint32_t data,unsigned flags) +{ + es_status st;if(!m || m->executing) return ES_EINVAL; + st=esk_esp32_uart_receive(&m->uart,data,flags);if(st==ES_OK) sync_inputs(m);return st; +} +es_status esk_esp32_machine_uart_timeout(esk_esp32_machine *m) +{ + es_status st;if(!m || m->executing) return ES_EINVAL; + st=esk_esp32_uart_timeout(&m->uart);if(st==ES_OK) sync_inputs(m);return st; +} +es_status esk_esp32_machine_uart_advance(esk_esp32_machine *m,uint64_t ticks,esk_esp32_uart_emit emit,void *ctx) +{ + es_status st;if(!m || m->executing) return ES_EINVAL; + m->executing=1;st=esk_esp32_uart_advance(&m->uart,ticks,emit,ctx);m->executing=0; + if(st==ES_OK) sync_inputs(m); + return st; +} diff --git a/core/mcu/esp32/machine.h b/core/mcu/esp32/machine.h index 1d6940aa4..ac8254cbd 100644 --- a/core/mcu/esp32/machine.h +++ b/core/mcu/esp32/machine.h @@ -5,6 +5,7 @@ #include "mcu/esp32/fabric.h" #include "mcu/esp32/matrix.h" #include "mcu/esp32/gpio.h" +#include "mcu/esp32/uart.h" #include "mcu/xtensa/windows.h" typedef struct esk_esp32_machine esk_esp32_machine; /* Experimental two-context execution harness over shared internal memory. @@ -35,6 +36,15 @@ es_status esk_esp32_machine_gpio_configure(esk_esp32_machine *m,const uint32_t * es_status esk_esp32_machine_gpio_input(esk_esp32_machine *m,unsigned pin,int known,int high); es_status esk_esp32_machine_gpio_output(const esk_esp32_machine *m,unsigned pin,esk_esp32_gpio_pad *pad); es_status esk_esp32_machine_gpio_auto_hold_event(esk_esp32_machine *m); +/* UART0 normal async fixture and externally supplied serial events; uart.h + * defines framing, clock and error/omission boundaries. Source 34 is owned + * while attached. Selected-clock ticks are independent of CPU retirement. + * APIs/callbacks are serialized between turns; callbacks must not re-enter. */ +es_status esk_esp32_machine_uart_configure(esk_esp32_machine *m,int on,int rxd,int cts,int dsr); +es_status esk_esp32_machine_uart_inputs(esk_esp32_machine *m,int rxd,int cts,int dsr); +es_status esk_esp32_machine_uart_receive(esk_esp32_machine *m,uint32_t data,unsigned flags); +es_status esk_esp32_machine_uart_timeout(esk_esp32_machine *m); +es_status esk_esp32_machine_uart_advance(esk_esp32_machine *m,uint64_t ticks,esk_esp32_uart_emit emit,void *ctx); /* Opt-in SPI1 owns matrix source 29; the eFuse profile owns source 44. * Both ownerships are active only while their respective profiles are enabled. * completion/enable changes route after commit (immediately for host bus writes). diff --git a/core/mcu/esp32/uart.c b/core/mcu/esp32/uart.c new file mode 100644 index 000000000..a87b70c68 --- /dev/null +++ b/core/mcu/esp32/uart.c @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "mcu/esp32/uart.h" +#include +#define BASE UINT32_C(0x3ff40000) +#define CONF0_MASK UINT32_C(0x0e0480ff) +#define CONF1_MASK UINT32_C(0xff007f7f) +#define IRQ_MASK UINT32_C(0x7ffff) +static int binary(int n) { return n==0 || n==1; } +static uint32_t divider16(const esk_esp32_uart *u) +{ return ((u->divider&0xfffffu)<<4)|((u->divider>>20)&15u); } +static void update(esk_esp32_uart *u) +{ + /* Threshold conditions reassert after clearing while still true. Other + * raw bits are events retained until W1C, independent of enable. */ + if(u->rx_count>(u->conf1&127u)) u->raw|=1u; + if(u->tx_count<((u->conf1>>8)&127u)) u->raw|=2u; +} +static void start(esk_esp32_uart *u) +{ + unsigned n,ones=0;uint32_t data; + if(u->active || !u->tx_count || (u->conf0&(1u<<18)) || ((u->conf0&(1u<<15)) && u->cts)) return; + data=u->tx[u->tx_read];u->tx_read=(u->tx_read+1u)%128u;u->tx_count--; + u->frame.bits=5u+((u->conf0>>2)&3u);data&=(1u<frame.bits)-1u; + for(n=0;nframe.bits;n++) ones+=(data>>n)&1u; + u->frame.data=data;u->frame.parity_enabled=(u->conf0>>1)&1u; + u->frame.parity=(ones&1u)^(u->conf0&1u);u->frame.stop_half_bits=((u->conf0>>4)&3u)==1u ? 2u : 3u; + u->frame.divider16=divider16(u);u->frame.apb=(u->conf0>>27)&1u; + u->half_bits=2u+2u*(u->frame.bits+u->frame.parity_enabled)+u->frame.stop_half_bits; + u->phase=0;u->active=1;u->remaining=u->frame.divider16;update(u); +} +static unsigned tx_state(const esk_esp32_uart *u) +{ + unsigned bit=u->phase/2u; + if(!u->active) return 0; + if(!bit) return 1; + if(bit<=u->frame.bits) return bit+1u; + if(u->frame.parity_enabled && bit==u->frame.bits+1u) return 10; + return u->phase>=4u+2u*(u->frame.bits+u->frame.parity_enabled) ? 12u : 11u; +} +static unsigned tx_level(const esk_esp32_uart *u) +{ + unsigned bit=u->phase/2u; + if(!u->active) return 1; + if(!bit) return 0; + if(bit<=u->frame.bits) return (u->frame.data>>(bit-1u))&1u; + if(u->frame.parity_enabled && bit==u->frame.bits+1u) return u->frame.parity; + return 1; +} +void esk_esp32_uart_init(esk_esp32_uart *u) { if(u) memset(u,0,sizeof *u); } +es_status esk_esp32_uart_configure(esk_esp32_uart *u,int on,int rxd,int cts,int dsr) +{ + if(!u || u->advancing || !binary(on) || !binary(rxd) || !binary(cts) || !binary(dsr)) return ES_EINVAL; + esk_esp32_uart_init(u);if(!on) return ES_OK; + u->enabled=1;u->conf0=0x0800001c;u->conf1=0x6060;u->divider=0x2b6; + u->rxd=(uint32_t)rxd;u->cts=(uint32_t)cts;u->dsr=(uint32_t)dsr;update(u);return ES_OK; +} +static int reg_offset(uint32_t address) +{ + if(address>=BASE && addressadvancing) return ES_EINVAL; + if(!u->enabled || off<0) return ES_EUNSUPPORTED; + if(bytes!=4 || (address&3u)) return ES_EINVAL; + switch(off) { + case 0: + if(!u->rx_count) return ES_EUNSUPPORTED; + result=u->rx[u->rx_read];u->rx_read=(u->rx_read+1u)%128u;u->rx_count--;update(u);break; + case 4: result=u->raw;break; + case 8: result=u->raw&u->ena;break; + case 12: result=u->ena;break; + case 20: result=u->divider;break; + case 28: result=u->rx_count|(u->tx_count<<16)|(tx_state(u)<<24)|(tx_level(u)<<31)| + (u->rxd<<15)|(u->cts<<14)|(u->dsr<<13)|((u->conf0&0x40u)<<24)|((u->conf0&0x80u)<<22);break; + case 32: result=u->conf0;break; + case 36: result=u->conf1;break; + case 96: result=(u->rx_read<<2)|(u->rx_write<<13);break; + default: return ES_EUNSUPPORTED; + } + *value=result;return ES_OK; +} +es_status esk_esp32_uart_write(esk_esp32_uart *u,uint32_t address,unsigned bytes,uint32_t value) +{ + int off=reg_offset(address);uint32_t masked; + if(!u || u->advancing) return ES_EINVAL; + if(!u->enabled || off<0) return ES_EUNSUPPORTED; + if(bytes!=4 || (address&3u)) return ES_EINVAL; + switch(off) { + case 0: + if(u->tx_count==128u || (u->conf0&(1u<<18))) return ES_EUNSUPPORTED; + u->tx[u->tx_write]=(uint8_t)(value&255u);u->tx_write=(u->tx_write+1u)%128u;u->tx_count++;start(u);break; + case 12: u->ena=value&IRQ_MASK;break; + case 16: u->raw&=~(value&IRQ_MASK);break; + case 20: + masked=value&0xffffffu; + if(!(masked&0xfffffu) || (u->active && masked!=u->divider)) return ES_EUNSUPPORTED; + u->divider=masked;break; + case 32: + masked=value&0xfffffffu; + if((masked&~CONF0_MASK) || ((masked>>4)&3u)==0u || ((masked>>4)&3u)==3u || + (u->active && masked!=u->conf0)) return ES_EUNSUPPORTED; + u->conf0=masked; + if(masked&(1u<<18)) u->tx_read=u->tx_write=u->tx_count=0; + start(u);break; + case 36: + if(value&~CONF1_MASK) return ES_EUNSUPPORTED; + u->conf1=value;break; + default: return ES_EUNSUPPORTED; + } + update(u);return ES_OK; +} +es_status esk_esp32_uart_inputs(esk_esp32_uart *u,int rxd,int cts,int dsr) +{ + if(!u || u->advancing || !binary(rxd) || !binary(cts) || !binary(dsr)) return ES_EINVAL; + if(!u->enabled) return ES_EUNSUPPORTED; + if(u->cts!=(uint32_t)cts) u->raw|=64u; + if(u->dsr!=(uint32_t)dsr) u->raw|=32u; + u->rxd=(uint32_t)rxd;u->cts=(uint32_t)cts;u->dsr=(uint32_t)dsr;start(u);update(u);return ES_OK; +} +es_status esk_esp32_uart_receive(esk_esp32_uart *u,uint32_t data,unsigned flags) +{ + if(!u || u->advancing || data>255u || flags>3u) return ES_EINVAL; + if(!u->enabled) return ES_EUNSUPPORTED; + if((flags&1u) && !(u->conf0&2u)) return ES_EINVAL; + u->raw|=(flags&1u)*4u+(flags&2u)*4u; + if(!flags || !(u->conf0&(1u<<26))) { + if(u->rx_count==128u) u->raw|=16u; + else { + u->rx[u->rx_write]=(uint8_t)(data&((1u<<(5u+((u->conf0>>2)&3u)))-1u)); + u->rx_write=(u->rx_write+1u)%128u;u->rx_count++; + } + } + update(u);return ES_OK; +} +es_status esk_esp32_uart_timeout(esk_esp32_uart *u) +{ + if(!u || u->advancing) return ES_EINVAL; + if(!u->enabled || !(u->conf1&UINT32_C(0x80000000)) || !u->rx_count) return ES_EUNSUPPORTED; + u->raw|=256u;return ES_OK; +} +es_status esk_esp32_uart_advance(esk_esp32_uart *u,uint64_t ticks,esk_esp32_uart_emit emit,void *ctx) +{ + uint64_t units; + if(!u || u->advancing || ticks>UINT64_MAX/32u) return ES_EINVAL; + if(!u->enabled) return ES_EUNSUPPORTED; + units=ticks*32u;u->advancing=1; + while(u->active && units>=u->remaining) { + units-=u->remaining;u->phase++; + if(u->phase==u->half_bits) { + u->active=0;u->remaining=0;if(emit) emit(ctx,&u->frame,ticks*32u-units); + if(!u->tx_count) u->raw|=UINT32_C(0x4000); + start(u); + } else u->remaining=u->frame.divider16; + } + if(u->active) u->remaining-=units; + u->advancing=0;update(u);return ES_OK; +} +es_status esk_esp32_uart_irq(const esk_esp32_uart *u,int *asserted) +{ + if(!u || !asserted) return ES_EINVAL; + if(!u->enabled) return ES_EUNSUPPORTED; + *asserted=(u->raw&u->ena)!=0;return ES_OK; +} diff --git a/core/mcu/esp32/uart.h b/core/mcu/esp32/uart.h new file mode 100644 index 000000000..15c127e4f --- /dev/null +++ b/core/mcu/esp32/uart.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_ESP32_UART_H +#define ESK_ESP32_UART_H +#include "embsim/types.h" +/* Opt-in UART0 normal asynchronous profile, fixed 128-byte FIFOs, empty on + * explicit host attach. External inputs RXD/CTS/DSR are required, not pin defaults. + * TX uses externally advanced selected source-clock ticks, with exact rational + * divider accounting (1/32 source-cycle units). No CPU-cycle/wall-time inference. + * Framing/divider configuration is immutable during a TX frame; unsupported changes + * fail unchanged. 5..8 data bits, parity, 1/1.5 stop supported. STOP=2 workaround, + * IRDA, RS485, autobaud, DMA, loopback, inversion, RX flow/software flow, break, + * idle insertion, power/gating/reset domains, UART1/2 and physical pin binding + * are absent. RX supplies completed decoded frames/errors, not sampled edges; + * RX timeout is an explicit external qualified event, not an invented duration. + * RX FIFO reset is deliberately unsupported: SDK documents a silicon issue and + * drains through APB instead. TX FIFO reset only while serializer idle. + * FIFO aliases APB/AHB 32-bit; empty read/full write fail unchanged rather than + * invent undefined bytes/drops. Unknown MMIO/WO reads fail. IRQ source 34. + * Serialized calls only; callbacks must not re-enter. NULL emit explicitly + * discards completed frames. Hold/pad voltage and USB bridge are not modeled. */ +typedef struct esk_esp32_uart_frame { + uint32_t data,bits,parity_enabled,parity,stop_half_bits,divider16,apb; +} esk_esp32_uart_frame; +/* Completion offset within the current advance call, in 1/32 source-cycle units. */ +typedef void (*esk_esp32_uart_emit)(void *ctx,const esk_esp32_uart_frame *frame,uint64_t offset32); +typedef struct esk_esp32_uart { + uint32_t enabled,conf0,conf1,divider,ena,raw,rxd,cts,dsr; + uint8_t tx[128],rx[128];unsigned tx_read,tx_write,tx_count,rx_read,rx_write,rx_count; + unsigned active,phase,half_bits,advancing;uint64_t remaining; + esk_esp32_uart_frame frame; +} esk_esp32_uart; +void esk_esp32_uart_init(esk_esp32_uart *u); +/* Initialize the standalone object before its first configuration. + * on=0 detach; on=1 attach/reset profile with explicit binary input levels. */ +es_status esk_esp32_uart_configure(esk_esp32_uart *u,int on,int rxd,int cts,int dsr); +es_status esk_esp32_uart_read(esk_esp32_uart *u,uint32_t address,unsigned bytes,uint32_t *value); +es_status esk_esp32_uart_write(esk_esp32_uart *u,uint32_t address,unsigned bytes,uint32_t value); +es_status esk_esp32_uart_inputs(esk_esp32_uart *u,int rxd,int cts,int dsr); +/* flags: bit0 parity error, bit1 frame error. Data is decoded before UART masking. + * Overflow latches RXFIFO_OVF and discards incoming data as specified by hardware. */ +es_status esk_esp32_uart_receive(esk_esp32_uart *u,uint32_t data,unsigned flags); +es_status esk_esp32_uart_timeout(esk_esp32_uart *u); +es_status esk_esp32_uart_advance(esk_esp32_uart *u,uint64_t ticks,esk_esp32_uart_emit emit,void *ctx); +es_status esk_esp32_uart_irq(const esk_esp32_uart *u,int *asserted); +#endif diff --git a/docs/STATUS.md b/docs/STATUS.md index 3e162b4d4..0adf83bc9 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -7,7 +7,16 @@ does not mean the described behaviour exists. ## Original ESP32 dual-core target investigation (2026-11-04) -**Latest GPIO update:** the [software-GPIO/pad-hold profile](engineering/esp32-gpio-pad-hold.md) +**Latest UART0 update:** the [normal asynchronous UART0 profile](engineering/esp32-uart0.md) +now supports fixed TX/RX FIFOs, explicit source-clock framing, CTS gating, decoded +RX/error input and interrupt source 34 on both native core views. 915,199 native +assertions, 316,077 independent bit/frame comparisons and 128 compiled C calls +with eleven unchanged SDK helpers pass. The additional explicit UART0 fixture +reaches 6,737 retirements, then UART1 INT_CLR write 0x3ff50010/PC 0x40009013. +UART1/2, advanced modes, full clocks/pin/electrical/USB integration and native SDK +boot remain unimplemented. Serial-clock advancement does not calibrate CPU timing. + +**Prior GPIO update:** the [software-GPIO/pad-hold profile](engineering/esp32-gpio-pad-hold.md) now supports explicit routes, live resolved inputs, projected drive/HiZ and hold capture/clear. With all explicit probe fixtures it reaches 6,734 instructions, then UART0 INT_CLR write at 0x3ff40010/PC 0x4000900b. Full GPIO/IO_MUX, electrical diff --git a/docs/engineering/esp32-boot-verification.md b/docs/engineering/esp32-boot-verification.md index 00910d01d..ef4b256ae 100644 --- a/docs/engineering/esp32-boot-verification.md +++ b/docs/engineering/esp32-boot-verification.md @@ -3,8 +3,8 @@ **Result on 2026-11-05: the owner's board reports ESP32-D0WD-V3 revision 3.1, JEDEC C4 60 16 and 4 MB flash. Giantec's GT25Q32A-U datasheet matches that ID; exact package/ordering suffix remains unconfirmed. The pinned SDK example boots -in Espressif QEMU. Native EmbSIM reaches UART0 initialization after 6,734 retired -instructions with explicit synthetic eFuse/RTC and active GPIO fixtures; without an eFuse profile, +in Espressif QEMU. Native EmbSIM reaches UART1 initialization after 6,737 retired +instructions with explicit synthetic eFuse/RTC, active GPIO and UART0 fixtures; without an eFuse profile, the baseline stops at an eFuse read after seven.** Neither result establishes successful native boot. @@ -114,7 +114,17 @@ configuration or printed SDK capacity must not substitute for hardware identity. ## Native EmbSIM result: blocked -**Latest follow-up:** [Software GPIO/global hold](esp32-gpio-pad-hold.md) now +**Latest UART0 follow-up:** [Normal UART0](esp32-uart0.md) adds native FIFOs, +source-clock framing and interrupt handling. With `--uart0-active-fixture` and +all prior explicit fixtures, the probe retires 6,737 instructions / 6,738 attempts, +then blocks on UART1 INT_CLR write 0x3ff50010, PC 0x40009013, opcode 0x68a2. +The added UART0 fixture supplies empty FIFOs and RXD=1/CTS=0/DSR=0; it advances no +serial ticks and does not infer board pinmux or other UARTs. The +[UART0 follow-up evidence](../../tests/vendor/xtensa_integer/evidence/boot-with-uart0-2026-11-05.json) +records the new source hashes and still reports boot unverified. Add +`--uart0-active-fixture` to the command below to reproduce this boundary. + +**Prior GPIO follow-up:** [Software GPIO/global hold](esp32-gpio-pad-hold.md) now handles the pad-hold portion of DIG_ISO with an explicit routing fixture. The probe reaches 6,734 retirements, blocked at UART0 INT_CLR write 0x3ff40010, PC 0x4000900b. The earlier 0x3ff48088 label was wrong: it is DIG_ISO, not ANA_CONF. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index a6716725b..92fb2c170 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -40,6 +40,10 @@ and the next digital-isolation-control read at 0x3ff48088/PC 0x4000770a. [Software GPIO and pad hold](esp32-gpio-pad-hold.md) adds another explicit fixture and advances the probe to UART0 INT_CLR after 6,734 instructions. Full GPIO/IO_MUX, board wiring and electrical loading remain unqualified. +[Normal UART0](esp32-uart0.md) adds fixed FIFOs, externally clocked framing and +interrupt delivery; its explicit fixture reaches 6,737 instructions before +UART1 INT_CLR write 0x3ff50010/PC 0x40009013. Other UARTs, advanced modes and +physical pin/clock/USB behavior remain absent. [Instruction timing](esp32-instruction-timing.md) still requires physical evidence; a buildable capture firmware is prepared without claiming calibrated latency. It is not the owner's fuse dump and does not establish full boot. The [Giantec storage subset](esp32-giantec-profile.md) diff --git a/docs/engineering/esp32-uart0.md b/docs/engineering/esp32-uart0.md new file mode 100644 index 000000000..8bd878364 --- /dev/null +++ b/docs/engineering/esp32-uart0.md @@ -0,0 +1,163 @@ +# Original ESP32 UART0 normal asynchronous profile + +**Implemented: opt-in UART0 normal asynchronous transmit/receive FIFOs, explicit +source-clock advancement, framing, CTS gating, interrupt latches and native +APB/AHB firmware access. This is not a complete UART, pin binding, USB bridge, +physical clock calibration or successful SDK boot. UART1/2 remain unsupported.** + +## Hardware and implementation boundary + +ESP32 TRM v5.9 chapter 19 describes three UARTs sharing 1024 bytes of RAM, with +128-byte TX and RX allocations by default. A transmitter removes a byte from its +FIFO into a separate serializer, then emits a low start, 5..8 data bits LSB first, +optional odd/even parity and a high stop. Its baud divider has a 20-bit integer +and four fractional bits, with APB or REF_TICK selected by CONF0. RX, error, +threshold, modem-change and completion interrupts have independent raw latches, +software enables and write-one clear controls. + +This original C99 implementation has no copied vendor runtime. It supports the +fixed UART0 allocation at APB 0x3ff40000 and FIFO alias 0x60000000. UART1/2, +allocation changes/shared-RAM interference, DPORT peripheral reset/clock gates, +DMA/UHCI, IRDA, RS485, autobaud, inversion, loopback, RX flow/software flow, +break detection/transmission, idle insertion, AT commands, wakeup and electrical +pad/USB behavior remain absent. Unsupported register/features/accesses return an +error unchanged; they do not acquire fabricated zero/default behavior. + +The pinned SDK documents hardware issues that constrain this profile: + +- `uart_ll_rxfifo_rst` drains through APB because the hardware RX reset bit is + unreliable. Setting that bit is unsupported here. The SDK helper itself also + inspects RX memory pointers; its complete reset procedure is not yet qualified. +- `uart_ll_set_stop_bits` uses RS485 `dl1_en` for the two-stop-bit workaround. + STOP encoding 3 and that workaround remain unsupported. One and 1.5 stop bits + are implemented; invalid encoding zero fails. +- UART1 TX reset also affects UART2. Neither port is inferred from UART0 or + exposed through an invented independent reset. + +## Explicit fixture and externally visible behavior + +Call `esk_esp32_machine_uart_configure` with on=1 and explicit binary RXD, CTS +and DSR levels. These are host fixture inputs, not inferred GPIO pinmux, board +wiring, pull state or physical captures. The standalone UART object must first +be initialized with `esk_esp32_uart_init`; the machine does this on creation. +Attach initializes empty FIFOs, CLKDIV=0x2b6, CONF0=0x0800001c, CONF1=0x6060 +and enables zero. Threshold conditions are immediately evaluated for the active +fixture; its raw value is not a claim about the instantaneous silicon reset edge. +Detach/re-attach is a debug fixture operation, not a SoC reset producer. + +- TX has 128 queued bytes plus a separate active frame. Moving the first queued + byte into an idle, unblocked serializer is immediate in this functional profile; + APB-to-UART synchronization latency is unqualified. Bytes are masked to the + configured character length when a frame starts. Completion emits the framed + character through an optional callback; a NULL callback explicitly discards it. +- `STATUS` reports FIFO counts, projected TX state/bit, supplied RXD/CTS/DSR and + software RTS/DTR controls. RX inputs represent completed frames, so this + profile has no in-progress receive FSM. TX FSM half-bit projections and FIFO + transitions follow explicit host clock turns, not instruction retirements. +- Hardware TX flow control stops the next frame while CTS is high. A frame + already in flight completes. CTS/DSR edges latch their change interrupts. + External input updates never rewrite the active frame. +- RX receives a decoded character plus explicit parity/frame-error flags. Enabled + parity checking is required for a parity-error event. ERR_WR_MASK discards + errored bytes while preserving error interrupts. Overflow discards the incoming + byte and latches RXFIFO_OVF; queued bytes retain order. Empty FIFO reads and full + TX writes fail unchanged rather than inventing undefined bytes or drop rules. +- FIFO word reads/writes use serialized, coherent APB/AHB access. RX memory + status exposes the fixed FIFO's relative modulo-128 read/write pointers for + the selected SDK length helper. Absolute shared-RAM layout, bus pipeline and + silicon FIFO-count errata are not qualified by that helper test. +- RX-full is strictly above its threshold; TX-empty is strictly below its + threshold. Raw events latch independently of enable. Clear pulses remove + selected events, and a threshold still true immediately reasserts. TX_DONE + occurs after the last queued frame completes, not merely when its FIFO drains. + Unsupported event producers cannot be injected through a blanket raw-bit API. +- RX timeout is an explicit external qualified timeout event. It requires enabled + timeout and unread RX data; the host is responsible for its timing. No guessed + timeout duration is derived from the differing SDK/TRM clock-unit descriptions. +- UART0 owns interrupt-matrix source 34 while attached. Both core views share + state. Firmware changes route after instruction commit; host changes route + immediately between turns. External injection into that owned slot fails. + The bounded CPU-only reset preserves this peripheral; global/reset-domain + behavior remains unsupported. + +MMIO is aligned 32-bit data access only. FIFO fetch/CAS, write-only clear reads, +unknown registers and feature-enabling writes remain unsupported. TX FIFO reset +is supported only while the serializer is idle; writes while reset is held fail. +Framing/divider changes during an active frame fail unchanged rather than +asserting an unverified mid-frame reconfiguration policy. Enable/clear/threshold +changes and RX access remain available during transmission. + +## Source-clock contract, separate from CPU timing + +`esk_esp32_machine_uart_advance` supplies ticks of the currently selected UART +clock. There is no automatic conversion from retired instructions, wall time, +CPU CCOUNT or an assumed APB frequency. A caller must supply the selected clock's +events and respect upstream clock gating; full SoC clock-tree integration is absent. + +Let `D = 16 * CLKDIV + CLKDIV_FRAG`. Internally, one half bit consumes `D` units, +where one unit is 1/32 of a source-clock cycle. Advancing one source tick supplies +32 units. This integer rational accumulator preserves fractional carry across +frames and chunked calls without rounded per-byte deadlines. Each completion +callback supplies its offset within the current advance call in 1/32 source-cycle +units, preserving distinct character times even when one call drains a queue. TX captures the +selected divider/framing at frame start. Zero integer dividers are unsupported. +The idle transmitter consumes no backlog of future time. + +Callbacks and all APIs are serialized and must not re-enter. Mutating standalone +UART APIs reject calls during clock advancement; machine APIs reject re-entry +while executing or advancing. Direct machine bus use is permitted only between +turns, as defined by the machine contract. No host callback may modify that bus. + +This is a functional serial-clock model. It does not qualify oscillator error, +PLL changes, jitter, synchronizers, metastability, CPU instruction timing, cache +stalls, bus contention, analog edges, real baud tolerance or silicon captures. + +## Evidence and reproduction + +Native `test_esp32_uart` passes **915,199 assertions**, including 8,192 frame +configurations, bit/state projections, CTS gating, FIFO ordering/wrap/overflow, +errors, W1C/reassertion, fractional deadline/chunking, unchanged failures, +reset hold, source ownership and fetched enable-store/next-instruction interrupt +entry. The sanitizer build runs the same tests. + +`tests/vendor/xtensa_integer/run_uart.py` independently constructs bitstreams from +pinned register fields: **316,077 half-bit/frame comparisons** pass. It also +compiles **eleven unchanged ESP-IDF v4.4.7 helper bodies** with the actual ESP32 +GCC 8.4 toolchain: **128 interactive C calls / 44,032 retired instructions** pass +on both native core buses. Firmware uses the real AHB TX/APB RX addresses, FIFO +length and interrupt helpers. The host advances the UART clock and supplies +received frames at a RAM handshake while firmware runs; no PC shortcut or +Python instruction-memory callback implements the UART operations. + +```sh +python3 tests/vendor/xtensa_integer/run_uart.py \ + --uart-reg "$IDF_PATH/components/soc/esp32/include/soc/uart_reg.h" \ + --uart-ll "$IDF_PATH/components/hal/esp32/include/hal/uart_ll.h" \ + --uart-struct "$IDF_PATH/components/soc/esp32/include/soc/uart_struct.h" \ + --misc "$IDF_PATH/components/hal/platform_port/include/hal/misc.h" \ + --compiler xtensa-esp32-elf-gcc --objcopy xtensa-esp32-elf-objcopy \ + --linker xtensa-esp32-elf-ld --nm xtensa-esp32-elf-nm +``` + +External SDK source is pinned to +[ESP-IDF v4.4.7 / 38eeba213aa695aabfd6d89aa9f5078dbe5a94c3](https://github.com/espressif/esp-idf/tree/38eeba213aa695aabfd6d89aa9f5078dbe5a94c3). +Selected helper bodies and headers retain their Apache-2.0 notices in temporary +build files. They are external test inputs, not bundled simulator runtime code. + +| External source | SHA-256 | +| --- | --- | +| ESP32 TRM v5.9 PDF, chapter 19 | `1007b90e6fafc16541d693d7444b63e1c5b01e46e60cce9b6810e02340a64b82` | +| `soc/esp32/include/soc/uart_reg.h` | `29d5e65d1dd560a339eb6057a93a1c8e2bcc90d730c82a8506fd894bd4869922` | +| `hal/esp32/include/hal/uart_ll.h` | `b98b4846e92480a24b6710b4dcd025a9c6951e4be37a95ce1fcd0c89df20dda5` | +| `soc/esp32/include/soc/uart_struct.h` | `4438b0d2289fa7acc417a6df9a5e4a024bcfe7932396955ad8594a907988be91` | +| `hal/platform_port/include/hal/misc.h` | `552e2d4ce5be7d93d228eb3b48bd959d0485290a7476ce5a7de438fe7ab9e152` | + +The [native ROM probe](esp32-boot-verification.md) adds +`--uart0-active-fixture` to the existing explicit eFuse/RTC/GPIO fixtures. It +supplies empty FIFOs and RXD=1/CTS=0/DSR=0, with no serial ticks. The ROM now retires +**6,737 instructions**, stopping at **UART1 INT_CLR write 0x3ff50010**, +PC **0x40009013**, opcode **0x68a2**. The +[hash-recorded evidence](../../tests/vendor/xtensa_integer/evidence/boot-with-uart0-2026-11-05.json) +retains `boot_verified: false`. Without this UART0 fixture, the previous UART0 +blocker remains reproducible. Neither this native failure nor reference QEMU +success qualifies physical-board or complete ROM/SDK boot. diff --git a/tests/unit/test_esp32_uart.c b/tests/unit/test_esp32_uart.c new file mode 100644 index 000000000..d25d81208 --- /dev/null +++ b/tests/unit/test_esp32_uart.c @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/machine.h" +#define BASE UINT32_C(0x3ff40000) +typedef struct capture { esk_esp32_uart_frame frames[129];unsigned count;uint64_t last_offset;esk_esp32_uart *u; } capture; +static void emit(void *ctx,const esk_esp32_uart_frame *frame,uint64_t offset32) +{ + capture *c=(capture *)ctx;CHECK(c->count<129);c->frames[c->count++]=*frame;c->last_offset=offset32; + if(c->u) CHECK_EQ(esk_esp32_uart_receive(c->u,0,0),ES_EINVAL); +} +static uint32_t read_reg(esk_esp32_uart *u,unsigned off) +{ + uint32_t v=0;CHECK_EQ(esk_esp32_uart_read(u,BASE+off,4,&v),ES_OK);return v; +} +static void frame_encoding_and_clock(void) +{ + esk_esp32_uart u;unsigned bits,parity,odd,stop,data,half,n,ones,level,state,total;capture c; + esk_esp32_uart_init(&u); + for(bits=5;bits<=8;bits++) for(parity=0;parity<2;parity++) for(odd=0;odd<2;odd++) for(stop=1;stop<=2;stop++) for(data=0;data<256;data++) { + memset(&c,0,sizeof c);c.u=&u;CHECK_EQ(esk_esp32_uart_configure(&u,1,1,0,0),ES_OK); + CHECK_EQ(esk_esp32_uart_write(&u,BASE+20,4,2),ES_OK); + CHECK_EQ(esk_esp32_uart_write(&u,BASE+32,4,0x08000000u|((bits-5u)<<2)|(parity<<1)|odd|(stop<<4)),ES_OK); + CHECK_EQ(esk_esp32_uart_write(&u,BASE,4,data),ES_OK);CHECK_EQ(u.tx_count,0); + total=2u+2u*(bits+parity)+(stop==1 ? 2u : 3u);ones=0; + for(n=0;n>n)&1u; + for(half=0;half>(bit-1u))&1u;state=bit+1u; } + else if(parity && bit==bits+1u) { level=(ones&1u)^odd;state=10; } + else { level=1;state=half>=4u+2u*(bits+parity) ? 12u : 11u; } + CHECK_EQ(status>>31,level);CHECK_EQ((status>>24)&15u,state); + CHECK_EQ(esk_esp32_uart_advance(&u,1,emit,&c),ES_OK); + CHECK_EQ(c.count,half+1u==total ? 1u : 0u); + } + CHECK_EQ(c.frames[0].data,data&((1u<>16)&255u,128);before=u; + CHECK_EQ(esk_esp32_uart_write(&u,BASE,4,129),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&u,&before,sizeof u),0); + CHECK_EQ(esk_esp32_uart_inputs(&u,1,0,1),ES_OK);CHECK_EQ(u.tx_count,127);CHECK_EQ(u.active,1); + CHECK_EQ(esk_esp32_uart_inputs(&u,1,1,1),ES_OK); + CHECK_EQ(esk_esp32_uart_advance(&u,1000000,emit,&c),ES_OK);CHECK_EQ(c.count,1);CHECK_EQ(u.tx_count,127); + CHECK_EQ(read_reg(&u,4)&0x4060u,0x60); /* no DONE while queued/CTS-blocked */ + CHECK_EQ(esk_esp32_uart_inputs(&u,1,0,0),ES_OK); + CHECK_EQ(esk_esp32_uart_advance(&u,1000000,emit,&c),ES_OK);CHECK_EQ(c.count,128); + for(n=0;n<128;n++) CHECK_EQ(c.frames[n].data,n); + CHECK_EQ(esk_esp32_uart_write(&u,BASE+12,4,UINT32_MAX),ES_OK);CHECK_EQ(read_reg(&u,12),0x7ffffu); + CHECK_EQ(esk_esp32_uart_irq(&u,&irq),ES_OK);CHECK_EQ(irq,1); + CHECK_EQ(esk_esp32_uart_write(&u,BASE+36,4,127u|(1u<<8)),ES_OK); + CHECK_EQ(esk_esp32_uart_write(&u,BASE+16,4,UINT32_MAX),ES_OK);CHECK_EQ(read_reg(&u,4),2); + for(round=0;round<3;round++) { + for(n=0;n<128;n++) CHECK_EQ(esk_esp32_uart_receive(&u,(round+n)&255u,0),ES_OK); + CHECK_EQ(read_reg(&u,4)&1u,1);CHECK_EQ(esk_esp32_uart_receive(&u,255,0),ES_OK); + CHECK_EQ(read_reg(&u,4)&16u,16);CHECK_EQ(read_reg(&u,28)&255u,128); + for(n=0;n<128;n++) CHECK_EQ(read_reg(&u,0),(round+n)&255u); + CHECK_EQ(read_reg(&u,96),0);value=0xdeadbeef; + CHECK_EQ(esk_esp32_uart_read(&u,BASE,4,&value),ES_EUNSUPPORTED);CHECK_EQ(value,0xdeadbeefu); + CHECK_EQ(read_reg(&u,4)&17u,17); /* latched until W1C */ + CHECK_EQ(esk_esp32_uart_write(&u,BASE+16,4,17),ES_OK);CHECK_EQ(read_reg(&u,4)&17u,0); + } + CHECK_EQ(esk_esp32_uart_timeout(&u),ES_EUNSUPPORTED); + CHECK_EQ(esk_esp32_uart_write(&u,BASE+32,4,0x0c00001e),ES_OK); + CHECK_EQ(esk_esp32_uart_receive(&u,0x55,3),ES_OK);CHECK_EQ(u.rx_count,0);CHECK_EQ(read_reg(&u,4)&12u,12); + CHECK_EQ(esk_esp32_uart_write(&u,BASE+32,4,0x0800001e),ES_OK); + CHECK_EQ(esk_esp32_uart_receive(&u,0xa5,3),ES_OK);CHECK_EQ(u.rx_count,1);CHECK_EQ(read_reg(&u,0),0xa5); + CHECK_EQ(esk_esp32_uart_write(&u,BASE+36,4,UINT32_C(0x8100017f)),ES_OK); + CHECK_EQ(esk_esp32_uart_receive(&u,0,0),ES_OK);CHECK_EQ(esk_esp32_uart_timeout(&u),ES_OK); + CHECK_EQ(read_reg(&u,4)&256u,256);CHECK_EQ(read_reg(&u,0),0); +} +static void fractional_clock_and_failures(void) +{ + esk_esp32_uart u,v,before;capture c,d;unsigned i;uint32_t value=7; + memset(&c,0,sizeof c);memset(&d,0,sizeof d);esk_esp32_uart_init(&u); + CHECK_EQ(esk_esp32_uart_configure(&u,1,1,0,0),ES_OK); + CHECK_EQ(esk_esp32_uart_write(&u,BASE+20,4,0x700002),ES_OK); + CHECK_EQ(esk_esp32_uart_write(&u,BASE,4,0xaa),ES_OK);v=u; + for(i=0;i<24;i++) CHECK_EQ(esk_esp32_uart_advance(&u,1,emit,&c),ES_OK); + CHECK_EQ(c.count,0);CHECK_EQ(esk_esp32_uart_advance(&u,1,emit,&c),ES_OK);CHECK_EQ(c.count,1); + CHECK_EQ(esk_esp32_uart_advance(&v,25,emit,&d),ES_OK);CHECK_EQ(d.count,1); + CHECK_EQ(memcmp(&u,&v,sizeof u),0);CHECK_EQ(memcmp(&c.frames[0],&d.frames[0],sizeof c.frames[0]),0); + CHECK_EQ(c.frames[0].divider16,39);CHECK_EQ(c.last_offset,12);CHECK_EQ(d.last_offset,780); + CHECK_EQ(esk_esp32_uart_write(&u,BASE,4,1),ES_OK);before=u; + CHECK_EQ(esk_esp32_uart_write(&u,BASE+20,4,2),ES_EUNSUPPORTED); + CHECK_EQ(esk_esp32_uart_write(&u,BASE+32,4,0x08000018),ES_EUNSUPPORTED); + CHECK_EQ(esk_esp32_uart_advance(&u,UINT64_MAX,NULL,NULL),ES_EINVAL); + CHECK_EQ(esk_esp32_uart_receive(&u,0,1),ES_EINVAL); + CHECK_EQ(esk_esp32_uart_receive(&u,256,0),ES_EINVAL); + CHECK_EQ(esk_esp32_uart_inputs(&u,2,0,0),ES_EINVAL);CHECK_EQ(memcmp(&u,&before,sizeof u),0); + CHECK_EQ(esk_esp32_uart_advance(&u,25,NULL,NULL),ES_OK); + before=u; + for(i=0;i<28;i++) if(!(UINT32_C(0x0e0480ff)&(1u<irq.enabled=1;w->irq.intenable=1u<<19;w->irq.vectors[2]=0x40084000;w->cpu.a[2]=BASE+12;w->cpu.a[3]=1; + CHECK_EQ(esk_esp32_machine_run_core(m,0,1),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK);CHECK_EQ(event,0);CHECK_EQ(w->cpu.pc,0x40080003); + CHECK(w->irq.lines&(1u<<19)); + CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK);CHECK_EQ(event,12);CHECK_EQ(w->irq.epc[2],0x40080003); + CHECK_EQ(b.write(b.ctx,BASE+12,4,0),ES_OK);CHECK(!(w->irq.lines&(1u<<19))); + CHECK_EQ(esk_esp32_machine_app_reset_configure(m,1),ES_OK); + CHECK_EQ(b.read(b.ctx,BASE,4,&value),ES_OK);CHECK_EQ(value,0xa5); + CHECK_EQ(b.fetch(b.ctx,BASE,3,&value),ES_EUNSUPPORTED); + CHECK_EQ(b.compare_exchange(b.ctx,BASE,0,0,&value),ES_EUNSUPPORTED); + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +static void reset_hold_and_masked_receive(void) +{ + esk_esp32_uart u,before;uint32_t value=0; + esk_esp32_uart_init(&u);CHECK_EQ(esk_esp32_uart_configure(&u,1,1,1,0),ES_OK); + CHECK_EQ(esk_esp32_uart_write(&u,BASE+32,4,0x08048010),ES_OK); + CHECK_EQ(read_reg(&u,32),0x08048010u);before=u; + CHECK_EQ(esk_esp32_uart_write(&u,BASE,4,1),ES_EUNSUPPORTED);CHECK_EQ(memcmp(&u,&before,sizeof u),0); + CHECK_EQ(esk_esp32_uart_write(&u,BASE+32,4,0x08008010),ES_OK); + CHECK_EQ(esk_esp32_uart_write(&u,BASE,4,255),ES_OK);CHECK_EQ(u.tx_count,1); + CHECK_EQ(esk_esp32_uart_write(&u,BASE+32,4,0x08048010),ES_OK);CHECK_EQ(u.tx_count,0); + CHECK_EQ(esk_esp32_uart_receive(&u,255,0),ES_OK);CHECK_EQ(read_reg(&u,0),31); + before=u;CHECK_EQ(esk_esp32_uart_configure(&u,1,1,2,0),ES_EINVAL);CHECK_EQ(memcmp(&u,&before,sizeof u),0); + CHECK_EQ(esk_esp32_uart_write(&u,BASE+32,4,0x080480d0),ES_OK); + value=read_reg(&u,28);CHECK(value&(1u<<30));CHECK(value&(1u<<29)); +} +TEST_MAIN_BEGIN + (void)t_str_eq;RUN(frame_encoding_and_clock);RUN(fifos_flow_and_errors);RUN(fractional_clock_and_failures);RUN(machine_routing_and_shared_state);RUN(fetched_enable_interrupt_and_retention);RUN(reset_hold_and_masked_receive); +TEST_MAIN_END diff --git a/tests/vendor/README.md b/tests/vendor/README.md index 1ca02084f..56f5bf3ab 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -240,3 +240,9 @@ and native boot remain unqualified. See the Input changes and held-output checks use native buses and explicit route/latch fixtures, not a complete IO_MUX/electrical/Deep-sleep/board model. See the [GPIO/pad-hold contract](../../docs/engineering/esp32-gpio-pad-hold.md). + +`xtensa_integer/run_uart.py` checks 316,077 independent UART0 bit/frame +observations and runs 128 interactive compiled C calls using eleven unchanged +ESP-IDF UART helpers on both native buses. Source clocks and decoded RX frames +are explicit host inputs; UART1/2, advanced modes and physical pin/clock/USB +behavior remain unqualified. See the [UART0 contract and reproduction](../../docs/engineering/esp32-uart0.md). diff --git a/tests/vendor/xtensa_integer/evidence/boot-with-uart0-2026-11-05.json b/tests/vendor/xtensa_integer/evidence/boot-with-uart0-2026-11-05.json new file mode 100644 index 000000000..4ece0e4de --- /dev/null +++ b/tests/vendor/xtensa_integer/evidence/boot-with-uart0-2026-11-05.json @@ -0,0 +1,95 @@ +{ + "boot_verified": false, + "outcome": "blocked", + "core": "PRO only", + "rom_sha256": "9115bbcd03da9f54d3baca5ad94d588a43da54ed332347137ba3dc9010df258b", + "flash_sha256": "52fcca3c31ed835d45e22826d42dd7a7db0c867171fa330f9c56b0a81df7bee7", + "reset_vector": "0x40000400", + "memctl_profile": "bit 0 plus deterministic 256-byte loop-buffer fixture; not physical fetch/timing qualification", + "region_profile": "original ESP32 eight ITLB/DTLB identity regions, reset attribute 2, one ring, no Xtensa cache", + "prid_fixture": "0x0000cdcd", + "prid_reference": "ESP-IDF v4.4.7 xtensa_context.h documents PRO=0xcdcd, APP=0xabab; not a hardware readout", + "rom_tail_policy": "zero-fill absent DROM tail to match QEMU region setup, not silicon contents", + "flash_profile": "immutable SPI1 fixture, not a physical flash identity", + "uart0_fixture": "normal UART0, empty FIFOs, explicit RXD=1 CTS=0 DSR=0, no serial-clock advancement, no UART1/2 or board pinmux", + "gpio_fixture": "no routed pads; defined active-domain DIG_ISO defaults and hold controls only, no isolation/power emulation", + "rtc_fixture": "power-on cause 1 per core, vector selects 1, eight explicit zero retention words; not physical-owner RTC state", + "efuse_fixture_sha256": "2d8192ea7bdf3510bc117b4ea4b833a94b2ff2ceff252ce1610c297ed633d5bb", + "efuse_completion_fixture": "one external completion opportunity every 64 instruction attempts; not physical timing", + "status": -1, + "event": 0, + "pc": "0x40009013", + "opcode": "0x68a2", + "address": "0x3ff50010", + "fault_kind": 6, + "attempts": 6738, + "region_readback": [ + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ], + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ] + ], + "region_access_attributes": [ + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ], + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ] + ], + "efuse_completions": 3, + "retired_before_stop": 6737, + "trace_tail": [ + "0x40008ff6", + "0x40008ff8", + "0x40008ffb", + "0x40008ffd", + "0x40008fff", + "0x40009002", + "0x40009005", + "0x40009008", + "0x4000900b", + "0x4000900d", + "0x40009010", + "0x40009013" + ], + "next_blocker": "UART1 UART_INT_CLR_REG write at 0x3ff50010, PC 0x40009013; native SDK boot remains unverified", + "fixture_note": "Explicit UART0 normal profile with empty FIFOs, RXD=1 CTS=0 DSR=0, no serial-clock advancement; not a board pinmux or electrical capture", + "sources_sha256": { + "core/mcu/esp32/uart.c": "0929a50c58a28629c908338089ebff83e887623bbca00bede0557cce756a4949", + "core/mcu/esp32/uart.h": "e81d48df006601707443bd8362c85b139b012f4aaedd002a06c56fcd7abf10fc", + "core/mcu/esp32/machine.c": "2d47c1909fce90b0e4135ba75ff992c678553e2e27d598f3a926e0a8bf1ec2fc", + "core/mcu/esp32/machine.h": "dd97cb6711bf78230485a20230ce63c0028cc3b5c7771b261bf71990a5495b80", + "tests/vendor/xtensa_integer/probe_rom_boot.py": "d26deb678dd5587e39a0115c7cbe2d19e84f7a0bd6ea671ae6fbe6647bbe751d" + } +} diff --git a/tests/vendor/xtensa_integer/probe_rom_boot.py b/tests/vendor/xtensa_integer/probe_rom_boot.py index 66c83d0ee..c8285744e 100644 --- a/tests/vendor/xtensa_integer/probe_rom_boot.py +++ b/tests/vendor/xtensa_integer/probe_rom_boot.py @@ -28,6 +28,7 @@ def main(): ap.add_argument('--rom',type=Path,required=True);ap.add_argument('--flash',type=Path,required=True) ap.add_argument('--efuse',type=Path,help='explicit JSON list of 31 raw None-coded fuse words; not inferred board defaults') ap.add_argument('--efuse-completion-period',type=int,default=64,help='external fixture event every N instruction attempts; not physical timing') + ap.add_argument('--uart0-active-fixture',action='store_true',help='normal UART0 fixture, explicit RXD=1 CTS=0 DSR=0, empty FIFOs, no serial ticks; not board pinmux or other UARTs') ap.add_argument('--gpio-active-fixture',action='store_true',help='explicit no-routed-pads software-GPIO/DIG_ISO hold fixture; isolation/power transitions unsupported') ap.add_argument('--rtc-power-on-fixture',action='store_true',help='explicit synthetic power-on reasons/vector selects and eight zero retention words; not owner RTC state') ap.add_argument('--prid',type=lambda value:int(value,0),default=0xcdcd,help='PRO PRID; SDK documents 0xcdcd; 0 reproduces historical invalid-ID fixture') @@ -42,7 +43,7 @@ def main(): with tempfile.TemporaryDirectory(prefix='embsim-native-rom-probe-') as tmp: lib=Path(tmp)/'probe.so' sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c', 'efuse.c','machine.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c', 'efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT/'core/util/alloc.c'] subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC', '-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) @@ -59,6 +60,7 @@ def bind(name,result,*params): complete=bind('esk_esp32_fabric_efuse_read_complete',C.c_int,pointer) spi=bind('esk_esp32_fabric_spi1_configure',C.c_int,pointer,C.c_int) core=bind('esk_esp32_machine_core',C.POINTER(Windows),pointer,C.c_uint) + uart=bind('esk_esp32_machine_uart_configure',C.c_int,pointer,C.c_int,C.c_int,C.c_int,C.c_int) gpio=bind('esk_esp32_machine_gpio_configure',C.c_int,pointer,C.POINTER(C.c_uint32),C.c_size_t) rtc=bind('esk_esp32_machine_rtc_configure',C.c_int,pointer,C.c_uint32,C.POINTER(C.c_uint32),C.c_size_t) memctl=bind('esk_xtensa_memctl_configure',C.c_int,C.POINTER(Windows)) @@ -78,6 +80,9 @@ def bind(name,result,*params): 'flash_profile':'immutable SPI1 fixture, not a physical flash identity'} try: ram=fabric(machine) + if args.uart0_active_fixture: + assert uart(machine,1,1,0,0)==0 + report['uart0_fixture']='normal UART0, empty FIFOs, explicit RXD=1 CTS=0 DSR=0, no serial-clock advancement, no UART1/2 or board pinmux' if args.gpio_active_fixture: config=(C.c_uint32*14)();assert gpio(machine,config,14)==0 report['gpio_fixture']='no routed pads; defined active-domain DIG_ISO defaults and hold controls only, no isolation/power emulation' diff --git a/tests/vendor/xtensa_integer/run_atomctl.py b/tests/vendor/xtensa_integer/run_atomctl.py index 501a3b380..dc4dbbf47 100644 --- a/tests/vendor/xtensa_integer/run_atomctl.py +++ b/tests/vendor/xtensa_integer/run_atomctl.py @@ -28,7 +28,7 @@ def main(): subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(obj),str(binary)],check=True) ops=binary.read_bytes();assert len(ops)==len(specs)*3 sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] sources += [ROOT/'core/util/alloc.c'];lib=out/'atomctl.so' subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) api=C.CDLL(str(lib));pointer=C.c_void_p;wp=C.POINTER(Windows) diff --git a/tests/vendor/xtensa_integer/run_controls.py b/tests/vendor/xtensa_integer/run_controls.py index a7f7f2b64..49832498d 100644 --- a/tests/vendor/xtensa_integer/run_controls.py +++ b/tests/vendor/xtensa_integer/run_controls.py @@ -38,7 +38,7 @@ def main(): with tempfile.TemporaryDirectory(prefix='embsim-controls-') as tmp: lib = Path(tmp) / 'controls.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','uart.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_efuse.py b/tests/vendor/xtensa_integer/run_efuse.py index a5eeebf25..a275daa2e 100644 --- a/tests/vendor/xtensa_integer/run_efuse.py +++ b/tests/vendor/xtensa_integer/run_efuse.py @@ -63,7 +63,7 @@ def get(value,name):mask,shift=field(name);return (value>>shift)&mask with tempfile.TemporaryDirectory(prefix='embsim-efuse-ref-') as tmp: out=Path(tmp);lib=out/'efuse.so' sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] sources += [ROOT/'core/util/alloc.c'] subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) api=C.CDLL(str(lib));pointer=C.c_void_p diff --git a/tests/vendor/xtensa_integer/run_flash_c.py b/tests/vendor/xtensa_integer/run_flash_c.py index 5fe00192f..b5e23001f 100644 --- a/tests/vendor/xtensa_integer/run_flash_c.py +++ b/tests/vendor/xtensa_integer/run_flash_c.py @@ -68,7 +68,7 @@ def compile_code(name, source, base, entry, flags=()): flash_image = bytes(image) lib = out / 'flash.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','uart.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_gpio.py b/tests/vendor/xtensa_integer/run_gpio.py index ffe51ebfc..f293557df 100644 --- a/tests/vendor/xtensa_integer/run_gpio.py +++ b/tests/vendor/xtensa_integer/run_gpio.py @@ -53,7 +53,7 @@ def address(name):return 0x3ff44000+int(re.search(r'#define\s+GPIO_'+name+r'_REG with tempfile.TemporaryDirectory(prefix='embsim-gpio-c-') as tmp: out=Path(tmp);lib=out/'gpio.so' sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] sources += [ROOT/'core/util/alloc.c'] subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) api=C.CDLL(str(lib));pointer=C.c_void_p diff --git a/tests/vendor/xtensa_integer/run_ipi_c.py b/tests/vendor/xtensa_integer/run_ipi_c.py index adb4bbae2..c0ea215b3 100644 --- a/tests/vendor/xtensa_integer/run_ipi_c.py +++ b/tests/vendor/xtensa_integer/run_ipi_c.py @@ -92,7 +92,7 @@ def main(): if len(f := line.split()) == 3} lib = out / 'ipi.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','uart.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_machine_c.py b/tests/vendor/xtensa_integer/run_machine_c.py index 8ec2a1de3..b9ce5f988 100644 --- a/tests/vendor/xtensa_integer/run_machine_c.py +++ b/tests/vendor/xtensa_integer/run_machine_c.py @@ -59,7 +59,7 @@ def main(): if len(f := line.split()) == 3} lib = out / 'machine.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','uart.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), diff --git a/tests/vendor/xtensa_integer/run_memctl.py b/tests/vendor/xtensa_integer/run_memctl.py index 2d29747fd..1a565b0c0 100644 --- a/tests/vendor/xtensa_integer/run_memctl.py +++ b/tests/vendor/xtensa_integer/run_memctl.py @@ -28,7 +28,7 @@ def main(): subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(obj),str(binary)],check=True) ops=binary.read_bytes();assert len(ops)==len(specs)*3 sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] sources += [ROOT/'core/util/alloc.c'];lib=out/'memctl.so' subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) api=C.CDLL(str(lib));pointer=C.c_void_p;wp=C.POINTER(Windows) diff --git a/tests/vendor/xtensa_integer/run_regions.py b/tests/vendor/xtensa_integer/run_regions.py index e35542ca5..7c2120272 100644 --- a/tests/vendor/xtensa_integer/run_regions.py +++ b/tests/vendor/xtensa_integer/run_regions.py @@ -34,7 +34,7 @@ def main(): subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(obj),str(binary)],check=True) ops=binary.read_bytes();assert len(ops)==len(specs)*3 sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] sources += [ROOT/'core/util/alloc.c'];lib=out/'regions.so' subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) api=C.CDLL(str(lib));pointer=C.c_void_p;wp=C.POINTER(Windows) diff --git a/tests/vendor/xtensa_integer/run_reset_c.py b/tests/vendor/xtensa_integer/run_reset_c.py index a9caa4ee7..0b644b61c 100644 --- a/tests/vendor/xtensa_integer/run_reset_c.py +++ b/tests/vendor/xtensa_integer/run_reset_c.py @@ -117,7 +117,7 @@ def compile_image(name, source, base, entry, assembly=False): rom = rom + bytes(0x60000 - len(rom)) # original test fixture, not a product ROM lib = out / 'reset.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','uart.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_rtc.py b/tests/vendor/xtensa_integer/run_rtc.py index b15905f99..9569c371b 100644 --- a/tests/vendor/xtensa_integer/run_rtc.py +++ b/tests/vendor/xtensa_integer/run_rtc.py @@ -38,7 +38,7 @@ def field(name): reason=int(re.search(r'\bEXT_CPU_RESET\s*=\s*(\d+)',header)[1]);reset_reg=address('RESET_STATE') with tempfile.TemporaryDirectory(prefix='embsim-rtc-') as tmp: lib=Path(tmp)/'rtc.so';sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] sources += [ROOT/'core/util/alloc.c'] subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) api=C.CDLL(str(lib));pointer=C.c_void_p diff --git a/tests/vendor/xtensa_integer/run_spi_c.py b/tests/vendor/xtensa_integer/run_spi_c.py index 85533ddd3..3211bbb2a 100644 --- a/tests/vendor/xtensa_integer/run_spi_c.py +++ b/tests/vendor/xtensa_integer/run_spi_c.py @@ -137,7 +137,7 @@ def compile_code(name,source,base,entry,flags=()): entries.append(entry);lengths.append(len(worker)) image=bytes(image);lib=out/'spi.so' sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c', 'efuse.c','machine.c','gpio.c','matrix.c','flashmap.c','flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c', 'efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT/'core/util/alloc.c'] subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC', '-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) diff --git a/tests/vendor/xtensa_integer/run_tls_c.py b/tests/vendor/xtensa_integer/run_tls_c.py index 7c05d4c82..d8ce14a60 100644 --- a/tests/vendor/xtensa_integer/run_tls_c.py +++ b/tests/vendor/xtensa_integer/run_tls_c.py @@ -83,7 +83,7 @@ def main(): assert int.from_bytes(template[symbols['zero']:symbols['zero'] + 4], 'little') == 0 lib = out / 'tls.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','uart.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_uart.py b/tests/vendor/xtensa_integer/run_uart.py new file mode 100644 index 000000000..066916402 --- /dev/null +++ b/tests/vendor/xtensa_integer/run_uart.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""UART0 bitstream referee and unchanged ESP-IDF helpers on native core buses. + +Source-clock ticks are explicit fixture input, not CPU instruction timing. +Decoded RX frames are external inputs; no waveform/pin/USB/board qualification. +""" +import argparse +import ctypes as C +from pathlib import Path +import random +import re +import subprocess +import tempfile +from run_execute import ROOT, Memory, Fault +from run_windows_c import Windows +from run_machine_c import Allocator +from run_matrix import read_header + + +class Frame(C.Structure): + _fields_=[(n,C.c_uint32) for n in ['data','bits','parity_enabled','parity','stop_half_bits','divider16','apb']] + + +def main(): + ap=argparse.ArgumentParser(description=__doc__) + for n in ['uart-reg','uart-ll','uart-struct','misc']:ap.add_argument('--'+n,type=Path,required=True) + for n in ['compiler','objcopy','linker','nm']:ap.add_argument('--'+n,required=True) + ap.add_argument('--cc',default='cc');args=ap.parse_args();rng=random.Random(0x4000900b) + reg=read_header(args.uart_reg,'29d5e65d1dd560a339eb6057a93a1c8e2bcc90d730c82a8506fd894bd4869922') + ll=read_header(args.uart_ll,'b98b4846e92480a24b6710b4dcd025a9c6951e4be37a95ce1fcd0c89df20dda5') + struct=read_header(args.uart_struct,'4438b0d2289fa7acc417a6df9a5e4a024bcfe7932396955ad8594a907988be91') + misc=read_header(args.misc,'552e2d4ce5be7d93d228eb3b48bd959d0485290a7476ce5a7de438fe7ab9e152') + def field(name):return int(re.search(r'#define UART_'+name+r'_S\s+(\d+)',reg)[1]) + def addr(name):return 0x3ff40000+int(re.search(r'#define UART_'+name+r'_REG\(i\)\s+\(REG_UART_BASE\(i\)\s*\+\s*(0x[0-9A-Fa-f]+)\)',reg)[1],16) + names=['ena_intr_mask','disable_intr_mask','get_intraw_mask','get_intsts_mask','clr_intsts_mask','get_intr_ena_status','read_rxfifo','write_txfifo','get_rxfifo_len','get_txfifo_len','txfifo_rst'] + helpers=[] + for name in names: + match=re.search(r'^(?:FORCE_INLINE_ATTR|static inline) [^\n]*\buart_ll_'+name+r'\([^\n]*\)\n\{',ll,re.M);assert match,name + end=match.end();depth=1 + while depth: + if ll[end]=='{':depth+=1 + elif ll[end]=='}':depth-=1 + end+=1 + helpers.append(ll[match.start():end]) + with tempfile.TemporaryDirectory(prefix='embsim-uart-c-') as tmp: + out=Path(tmp);lib=out/'uart.so' + sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/util/alloc.c'] + subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) + api=C.CDLL(str(lib));pointer=C.c_void_p + def bind(name,result,*params):f=getattr(api,name);f.restype=result;f.argtypes=list(params);return f + ai=bind('esk_alloc_init',None,C.POINTER(Allocator),pointer);create=bind('esk_esp32_machine_create',C.c_int,C.POINTER(Allocator),C.POINTER(pointer));destroy=bind('esk_esp32_machine_destroy',None,pointer) + attach=bind('esk_esp32_machine_uart_configure',C.c_int,pointer,C.c_int,C.c_int,C.c_int,C.c_int) + receive=bind('esk_esp32_machine_uart_receive',C.c_int,pointer,C.c_uint32,C.c_uint) + callback=C.CFUNCTYPE(None,pointer,C.POINTER(Frame),C.c_uint64);frames=[];times=[] + def emitted(ctx,frame,offset32): + frames.append(tuple(getattr(frame.contents,n) for n,_ in Frame._fields_));times.append(offset32) + emit=callback(emitted) + advance=bind('esk_esp32_machine_uart_advance',C.c_int,pointer,C.c_uint64,callback,pointer) + bus_get=bind('esk_esp32_machine_bus',Memory,pointer,C.c_uint);fabric=bind('esk_esp32_machine_fabric',pointer,pointer) + load=bind('esk_esp32_fabric_load',C.c_int,pointer,C.c_uint,C.c_uint32,pointer,C.c_size_t) + configure=bind('esk_esp32_machine_configure',C.c_int,pointer,C.c_uint,C.c_uint32);core=bind('esk_esp32_machine_core',C.POINTER(Windows),pointer,C.c_uint) + run=bind('esk_esp32_machine_run_core',C.c_int,pointer,C.c_uint,C.c_int);step=bind('esk_esp32_machine_step',C.c_int,pointer,C.c_uint,C.POINTER(C.c_uint),C.POINTER(Fault)) + allocator=Allocator();ai(C.byref(allocator),None);m=pointer();assert create(C.byref(allocator),C.byref(m))==0 + comparisons=0;calls=0;instructions=0;value=C.c_uint32();event=C.c_uint();fault=Fault() + try: + bus=bus_get(m,0) + for trial in range(512): + assert attach(m,1,1,0,0)==0;frames.clear();times.clear() + bits=rng.randrange(5,9);parity=rng.randrange(2);odd=rng.randrange(2);stop=rng.randrange(1,3);data=rng.randrange(256);div=rng.randrange(2,130);frag=rng.randrange(16);apb=rng.randrange(2) + conf=(apb<>n)&1 for n in range(bits)] + p=(sum(bitstream)%2)^odd + if parity:bitstream.append(p) + halves=[b for b in bitstream for _ in range(2)]+[1]*(2 if stop==1 else 3) + divider=div*16+frag;duration=divider*len(halves);tick=0 + while tick*32>field('TXD'))&1==halves[tick*32//divider],(trial,tick,value.value,halves) + comparisons+=1;assert advance(m,1,emit,None)==0;tick+=1 + assert frames==[(data&((1<\n#define DR_REG_UART_BASE 0x3ff40000\n#define BIT(n) (1U<<(n))\n',encoding='utf-8') + source=ll[:ll.index('#pragma once')]+''' +#include +#include +#include "uart_struct.h" +#include "uart_reg.h" +#include "misc.h" +extern uart_dev_t UART0,UART1,UART2; +#define FORCE_INLINE_ATTR static inline __attribute__((always_inline)) +#define UART_LL_FIFO_DEF_LEN 128 +#define READ_PERI_REG(r) (*(volatile uint32_t *)(r)) +#define WRITE_PERI_REG(r,v) (*(volatile uint32_t *)(r)=(v)) +'''+ '\n'.join(helpers)+''' +unsigned work(unsigned seed) { + uart_dev_t *hw=&UART0; + volatile unsigned *phase=(volatile unsigned *)0x3ffb9000; + uint8_t tx[16],rx[16];unsigned sum=0; + uart_ll_disable_intr_mask(hw,0x7ffff);uart_ll_clr_intsts_mask(hw,0x7ffff); + uart_ll_txfifo_rst(hw); + for(unsigned i=0;i<16;i++) tx[i]=(uint8_t)(seed+i); + if(uart_ll_get_txfifo_len(hw)!=128) return 0xbad00001; + uart_ll_ena_intr_mask(hw,1); + if(uart_ll_get_intr_ena_status(hw)!=1) return 0xbad00002; + uart_ll_write_txfifo(hw,tx,16); + *phase=1;while(*phase!=2) { } + if(uart_ll_get_rxfifo_len(hw)!=16) return 0xbad00003; + if(!(uart_ll_get_intsts_mask(hw)&1) || !(uart_ll_get_intraw_mask(hw)&1)) return 0xbad00004; + uart_ll_read_rxfifo(hw,rx,16); + for(unsigned i=0;i<16;i++) sum+=rx[i]; + uart_ll_clr_intsts_mask(hw,1);uart_ll_disable_intr_mask(hw,1); + return sum; +} +''' + src=out/'work.c';obj=out/'work.o';elf=out/'work.elf';binary=out/'work.bin';src.write_text(source,encoding='utf-8') + subprocess.run([args.compiler,'-B'+str(Path(args.compiler).parent)+'/','-O1','-ffreestanding','-fno-builtin','-mtext-section-literals','-c',str(src),'-o',str(obj)],check=True) + subprocess.run([args.linker,'--no-relax','-Ttext=0x40080000','--entry=work','--defsym=UART0=0x3ff40000','--defsym=UART1=0x3ff50000','--defsym=UART2=0x3ff6e000',str(obj),'-o',str(elf)],check=True) + subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(elf),str(binary)],check=True) + symbols={f[2]:int(f[0],16) for line in subprocess.check_output([args.nm,str(elf)],text=True).splitlines() if len(f:=line.split())==3} + code=binary.read_bytes();assert load(fabric(m),0,0x40080000,C.c_char_p(code),len(code))==0 + assert bus.write(bus.ctx,0x3ff00030,4,1)==0 + for cpu in range(2): + for trial in range(64): + assert attach(m,1,1,0,0)==0;frames.clear();times.clear() + assert bus.write(bus.ctx,addr('CONF1'),4,7|(96<<8))==0 + assert bus.write(bus.ctx,0x3ffb9000,4,0)==0 + assert configure(m,cpu,symbols['work'])==0;w=core(m,cpu).contents;w.ps|=1<<16 + seed=rng.randrange(256);w.cpu.a[1]=0x3ffd0000+cpu*4096;w.cpu.a[4]=0x7ffffff0;w.cpu.a[6]=seed + assert run(m,cpu,1)==0;injected=False;rx=[rng.randrange(256) for _ in range(16)] + for turn in range(6000): + if w.cpu.pc==0x7ffffff0:break + assert step(m,cpu,C.byref(event),C.byref(fault))==0 and event.value==0,(hex(fault.pc),hex(fault.opcode),hex(fault.address),event.value) + instructions+=1 + assert bus.read(bus.ctx,0x3ffb9000,4,C.byref(value))==0 + if value.value==1 and not injected: + assert advance(m,1000000,emit,None)==0 + assert [f[0] for f in frames]==[(seed+i)&255 for i in range(16)] + assert times==[(i+1)*20*16*0x2b6 for i in range(16)] + for data in rx:assert receive(m,data,0)==0 + assert bus.write(bus.ctx,0x3ffb9000,4,2)==0;injected=True + else:raise AssertionError('UART firmware budget exceeded') + assert injected and w.cpu.a[6]==sum(rx),(cpu,trial,hex(w.cpu.a[6]),sum(rx)) + calls+=1 + finally:destroy(m) + assert allocator.live_bytes==0 + print(f'{comparisons} independent UART half-bit/frame comparisons; {calls} actual ESP32 C interactive calls, {instructions} instructions, eleven unchanged SDK helpers; FIFO, IRQ and native APB/AHB paths') + + +if __name__=='__main__':main() From 09e19121fde6f1fee46210621d4cd9ec6aaf0824 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 22:09:24 +0000 Subject: [PATCH 37/57] Implement ESP32 UART bank shared RAM and coupled FIFO reset --- CHANGELOG.md | 14 ++ core/mcu/esp32/machine.c | 65 ++++--- core/mcu/esp32/machine.h | 14 +- core/mcu/esp32/uart.c | 118 ++++++++++--- core/mcu/esp32/uart.h | 21 ++- docs/STATUS.md | 11 +- docs/engineering/esp32-boot-verification.md | 16 +- docs/engineering/esp32-dual-core.md | 4 + docs/engineering/esp32-uart-bank.md | 160 +++++++++++++++++ docs/engineering/esp32-uart0.md | 16 +- tests/unit/test_esp32_uart_bank.c | 167 ++++++++++++++++++ tests/vendor/README.md | 6 + .../boot-with-uart-bank-2026-11-05.json | 95 ++++++++++ tests/vendor/xtensa_integer/probe_rom_boot.py | 8 +- tests/vendor/xtensa_integer/run_uart.py | 135 +++++++++++--- 15 files changed, 764 insertions(+), 86 deletions(-) create mode 100644 docs/engineering/esp32-uart-bank.md create mode 100644 tests/unit/test_esp32_uart_bank.c create mode 100644 tests/vendor/xtensa_integer/evidence/boot-with-uart-bank-2026-11-05.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 854f7e94a..16f4d43e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Original ESP32 UART bank and shared FIFO RAM + +- Extended normal asynchronous execution to UART1/2, with per-port sources + 34/35/36, host inputs/clocks and shared state on both CPU buses. +- Added one physical-layout FIFO byte array, bounded block allocations, + overlapping queued-data overwrites, extended counts and high thresholds. +- Added idle-only UART1→UART2 TX reset coupling, held-reset protection and + unchanged failure for active/invalid resets. RX reset remains unsupported; + the unchanged SDK drain helper is tested on every port instead. +- 271,883 new native assertions, 4,096 shared-RAM oracle turns and 384 compiled + C calls with twelve unchanged SDK helpers pass. The prior UART0 suite remains + passing. Native ROM now reaches 6,790 retirements, then instruction fetch at + 0x40065c18 outside the current ROM mapping. Full SDK boot remains unverified. + ### Original ESP32 normal asynchronous UART0 - Added an opt-in fixed-FIFO UART0 profile with APB/AHB firmware access, diff --git a/core/mcu/esp32/machine.c b/core/mcu/esp32/machine.c index 49e579997..02b7c7457 100644 --- a/core/mcu/esp32/machine.c +++ b/core/mcu/esp32/machine.c @@ -7,9 +7,9 @@ struct esk_esp32_machine { esk_alloc *a;esk_esp32_fabric *fabric; esk_xtensa_windows cpu[2];uint64_t retired[2];unsigned running[2],executing; uint32_t app_clock,app_stall,app_boot,app_reset,app_reset_enabled,app_reset_vector,app_reset_pending; - unsigned spi1_owned,efuse_owned,uart_owned; + unsigned spi1_owned,efuse_owned,uart_owned[3]; uint32_t rtc_enabled,rtc_state,rtc_store[8]; - esk_esp32_gpio gpio;esk_esp32_uart uart; + esk_esp32_gpio gpio;esk_esp32_uart_bank uart; esk_esp32_matrix matrix;struct machine_view view[2]; }; /* Never mutate an executing CPU from a bus callback. Synchronize the @@ -29,10 +29,12 @@ static void sync_inputs(esk_esp32_machine *m) } else if(m->efuse_owned) { (void)esk_esp32_matrix_source(&m->matrix,44,0);m->efuse_owned=0; } - if(esk_esp32_uart_irq(&m->uart,&asserted)==ES_OK) { - (void)esk_esp32_matrix_source(&m->matrix,34,asserted);m->uart_owned=1; - } else if(m->uart_owned) { - (void)esk_esp32_matrix_source(&m->matrix,34,0);m->uart_owned=0; + for(input=0;input<3;input++) { + if(esk_esp32_uart_irq(&m->uart.port[input],&asserted)==ES_OK) { + (void)esk_esp32_matrix_source(&m->matrix,34u+input,asserted);m->uart_owned[input]=1; + } else if(m->uart_owned[input]) { + (void)esk_esp32_matrix_source(&m->matrix,34u+input,0);m->uart_owned[input]=0; + } } for(cpu=0;cpu<2;cpu++) if(m->cpu[cpu].irq.enabled && !(cpu==1 && m->app_reset_enabled && m->app_reset)) { uint32_t levels=esk_esp32_matrix_levels(&m->matrix,cpu); @@ -107,7 +109,7 @@ static es_status bus_read(void *ctx,uint32_t address,unsigned bytes,uint32_t *va { struct machine_view *v=(struct machine_view *)ctx;esk_xtensa_memory ram;es_status st; st=control_read(v->m,address,bytes,value);if(st!=ES_EUNSUPPORTED) return st; - st=esk_esp32_uart_read(&v->m->uart,address,bytes,value); + st=esk_esp32_uart_bank_read(&v->m->uart,address,bytes,value); if(st!=ES_EUNSUPPORTED) { if(st==ES_OK && !v->m->executing) sync_inputs(v->m);return st; } st=esk_esp32_gpio_read(&v->m->gpio,address,bytes,value);if(st!=ES_EUNSUPPORTED) return st; st=esk_esp32_matrix_read(&v->m->matrix,address,bytes,value); @@ -122,7 +124,7 @@ static es_status bus_write(void *ctx,uint32_t address,unsigned bytes,uint32_t va if(st==ES_OK && !v->m->executing) { apply_reset(v->m);sync_inputs(v->m); } return st; } - st=esk_esp32_uart_write(&v->m->uart,address,bytes,value); + st=esk_esp32_uart_bank_write(&v->m->uart,address,bytes,value); if(st!=ES_EUNSUPPORTED) { if(st==ES_OK && !v->m->executing) sync_inputs(v->m);return st; } st=esk_esp32_gpio_write(&v->m->gpio,address,bytes,value);if(st!=ES_EUNSUPPORTED) return st; st=esk_esp32_matrix_write(&v->m->matrix,address,bytes,value); @@ -166,9 +168,9 @@ es_status esk_esp32_machine_source(esk_esp32_machine *m,unsigned source,int asse if(esk_esp32_fabric_efuse_irq(m->fabric,&asserted_now)==ES_OK) return ES_EUNSUPPORTED; if(m->efuse_owned) sync_inputs(m); } - if(source==34) { - if(esk_esp32_uart_irq(&m->uart,&asserted_now)==ES_OK) return ES_EUNSUPPORTED; - if(m->uart_owned) sync_inputs(m); + if(source>=34 && source<=36) { + if(esk_esp32_uart_irq(&m->uart.port[source-34u],&asserted_now)==ES_OK) return ES_EUNSUPPORTED; + if(m->uart_owned[source-34u]) sync_inputs(m); } st=esk_esp32_matrix_source(&m->matrix,source,asserted);if(st==ES_OK) sync_inputs(m);return st; } @@ -210,7 +212,7 @@ es_status esk_esp32_machine_create(esk_alloc *a,esk_esp32_machine **out) m=(esk_esp32_machine *)esk_calloc(a,1,sizeof *m);if(!m) return ES_ENOMEM; m->a=a;st=esk_esp32_fabric_create(a,&m->fabric); if(st!=ES_OK) { esk_free(a,m,sizeof *m);return st; } - esk_esp32_matrix_init(&m->matrix);esk_esp32_gpio_init(&m->gpio);esk_esp32_uart_init(&m->uart); + esk_esp32_matrix_init(&m->matrix);esk_esp32_gpio_init(&m->gpio);esk_esp32_uart_bank_init(&m->uart); for(i=0;i<2;i++) { esk_xtensa_windows_init(&m->cpu[i],0,NULL);m->view[i].m=m;m->view[i].cpu=i; } *out=m;return ES_OK; } @@ -271,30 +273,41 @@ es_status esk_esp32_machine_gpio_output(const esk_esp32_machine *m,unsigned pin, es_status esk_esp32_machine_gpio_auto_hold_event(esk_esp32_machine *m) { return !m || m->executing ? ES_EINVAL : esk_esp32_gpio_auto_hold_event(&m->gpio); } -es_status esk_esp32_machine_uart_configure(esk_esp32_machine *m,int on,int rxd,int cts,int dsr) +es_status esk_esp32_machine_uart_port_configure(esk_esp32_machine *m,unsigned port,int on,int rxd,int cts,int dsr) { es_status st;if(!m || m->executing) return ES_EINVAL; - st=esk_esp32_uart_configure(&m->uart,on,rxd,cts,dsr);if(st==ES_OK) sync_inputs(m);return st; + st=esk_esp32_uart_bank_configure(&m->uart,port,on,rxd,cts,dsr);if(st==ES_OK) sync_inputs(m);return st; } -es_status esk_esp32_machine_uart_inputs(esk_esp32_machine *m,int rxd,int cts,int dsr) +es_status esk_esp32_machine_uart_port_inputs(esk_esp32_machine *m,unsigned port,int rxd,int cts,int dsr) { - es_status st;if(!m || m->executing) return ES_EINVAL; - st=esk_esp32_uart_inputs(&m->uart,rxd,cts,dsr);if(st==ES_OK) sync_inputs(m);return st; + es_status st;if(!m || m->executing || port>2) return ES_EINVAL; + st=esk_esp32_uart_inputs(&m->uart.port[port],rxd,cts,dsr);if(st==ES_OK) sync_inputs(m);return st; } -es_status esk_esp32_machine_uart_receive(esk_esp32_machine *m,uint32_t data,unsigned flags) +es_status esk_esp32_machine_uart_port_receive(esk_esp32_machine *m,unsigned port,uint32_t data,unsigned flags) { - es_status st;if(!m || m->executing) return ES_EINVAL; - st=esk_esp32_uart_receive(&m->uart,data,flags);if(st==ES_OK) sync_inputs(m);return st; + es_status st;if(!m || m->executing || port>2) return ES_EINVAL; + st=esk_esp32_uart_receive(&m->uart.port[port],data,flags);if(st==ES_OK) sync_inputs(m);return st; } -es_status esk_esp32_machine_uart_timeout(esk_esp32_machine *m) +es_status esk_esp32_machine_uart_port_timeout(esk_esp32_machine *m,unsigned port) { - es_status st;if(!m || m->executing) return ES_EINVAL; - st=esk_esp32_uart_timeout(&m->uart);if(st==ES_OK) sync_inputs(m);return st; + es_status st;if(!m || m->executing || port>2) return ES_EINVAL; + st=esk_esp32_uart_timeout(&m->uart.port[port]);if(st==ES_OK) sync_inputs(m);return st; } -es_status esk_esp32_machine_uart_advance(esk_esp32_machine *m,uint64_t ticks,esk_esp32_uart_emit emit,void *ctx) +es_status esk_esp32_machine_uart_port_advance(esk_esp32_machine *m,unsigned port,uint64_t ticks,esk_esp32_uart_emit emit,void *ctx) { - es_status st;if(!m || m->executing) return ES_EINVAL; - m->executing=1;st=esk_esp32_uart_advance(&m->uart,ticks,emit,ctx);m->executing=0; + es_status st;if(!m || m->executing || port>2) return ES_EINVAL; + m->executing=1;st=esk_esp32_uart_advance(&m->uart.port[port],ticks,emit,ctx);m->executing=0; if(st==ES_OK) sync_inputs(m); return st; } +/* Preserve UART0 test/host callers while exposing explicit port selection. */ +es_status esk_esp32_machine_uart_configure(esk_esp32_machine *m,int on,int rxd,int cts,int dsr) +{ return esk_esp32_machine_uart_port_configure(m,0,on,rxd,cts,dsr); } +es_status esk_esp32_machine_uart_inputs(esk_esp32_machine *m,int rxd,int cts,int dsr) +{ return esk_esp32_machine_uart_port_inputs(m,0,rxd,cts,dsr); } +es_status esk_esp32_machine_uart_receive(esk_esp32_machine *m,uint32_t data,unsigned flags) +{ return esk_esp32_machine_uart_port_receive(m,0,data,flags); } +es_status esk_esp32_machine_uart_timeout(esk_esp32_machine *m) +{ return esk_esp32_machine_uart_port_timeout(m,0); } +es_status esk_esp32_machine_uart_advance(esk_esp32_machine *m,uint64_t ticks,esk_esp32_uart_emit emit,void *ctx) +{ return esk_esp32_machine_uart_port_advance(m,0,ticks,emit,ctx); } diff --git a/core/mcu/esp32/machine.h b/core/mcu/esp32/machine.h index ac8254cbd..bfb85f564 100644 --- a/core/mcu/esp32/machine.h +++ b/core/mcu/esp32/machine.h @@ -36,10 +36,16 @@ es_status esk_esp32_machine_gpio_configure(esk_esp32_machine *m,const uint32_t * es_status esk_esp32_machine_gpio_input(esk_esp32_machine *m,unsigned pin,int known,int high); es_status esk_esp32_machine_gpio_output(const esk_esp32_machine *m,unsigned pin,esk_esp32_gpio_pad *pad); es_status esk_esp32_machine_gpio_auto_hold_event(esk_esp32_machine *m); -/* UART0 normal async fixture and externally supplied serial events; uart.h - * defines framing, clock and error/omission boundaries. Source 34 is owned - * while attached. Selected-clock ticks are independent of CPU retirement. - * APIs/callbacks are serialized between turns; callbacks must not re-enter. */ +/* UART bank normal async fixtures and externally supplied serial events; + * uart.h defines shared RAM, reset coupling, clock/framing and limitations. + * Sources 34..36 are owned per attached port. Selected-clock ticks are independent + * of CPU retirement. Calls/callbacks are serialized between turns; no re-entry. */ +es_status esk_esp32_machine_uart_port_configure(esk_esp32_machine *m,unsigned port,int on,int rxd,int cts,int dsr); +es_status esk_esp32_machine_uart_port_inputs(esk_esp32_machine *m,unsigned port,int rxd,int cts,int dsr); +es_status esk_esp32_machine_uart_port_receive(esk_esp32_machine *m,unsigned port,uint32_t data,unsigned flags); +es_status esk_esp32_machine_uart_port_timeout(esk_esp32_machine *m,unsigned port); +es_status esk_esp32_machine_uart_port_advance(esk_esp32_machine *m,unsigned port,uint64_t ticks,esk_esp32_uart_emit emit,void *ctx); +/* Compatibility helpers below address UART0. */ es_status esk_esp32_machine_uart_configure(esk_esp32_machine *m,int on,int rxd,int cts,int dsr); es_status esk_esp32_machine_uart_inputs(esk_esp32_machine *m,int rxd,int cts,int dsr); es_status esk_esp32_machine_uart_receive(esk_esp32_machine *m,uint32_t data,unsigned flags); diff --git a/core/mcu/esp32/uart.c b/core/mcu/esp32/uart.c index a87b70c68..bf418335d 100644 --- a/core/mcu/esp32/uart.c +++ b/core/mcu/esp32/uart.c @@ -2,7 +2,14 @@ /* Copyright 2026 Junior Deogracias */ #include "mcu/esp32/uart.h" #include -#define BASE UINT32_C(0x3ff40000) +static uint32_t apb_base(unsigned port) +{ return port==0 ? UINT32_C(0x3ff40000) : port==1 ? UINT32_C(0x3ff50000) : UINT32_C(0x3ff6e000); } +static uint32_t ahb_fifo(unsigned port) +{ return port==0 ? UINT32_C(0x60000000) : port==1 ? UINT32_C(0x60010000) : UINT32_C(0x6002e000); } +static unsigned capacity(const esk_esp32_uart *u,int rx) +{ return 128u*((u->memconf>>(rx ? 3u : 7u))&15u); } +static uint8_t *storage(esk_esp32_uart *u,int rx) +{ return u->shared ? &u->shared[128u*(u->port+(rx ? 3u : 0u))] : rx ? u->rx : u->tx; } #define CONF0_MASK UINT32_C(0x0e0480ff) #define CONF1_MASK UINT32_C(0xff007f7f) #define IRQ_MASK UINT32_C(0x7ffff) @@ -13,14 +20,14 @@ static void update(esk_esp32_uart *u) { /* Threshold conditions reassert after clearing while still true. Other * raw bits are events retained until W1C, independent of enable. */ - if(u->rx_count>(u->conf1&127u)) u->raw|=1u; - if(u->tx_count<((u->conf1>>8)&127u)) u->raw|=2u; + if(u->rx_count>((u->conf1&127u)|(((u->memconf>>25)&7u)<<7))) u->raw|=1u; + if(u->tx_count<(((u->conf1>>8)&127u)|(((u->memconf>>28)&7u)<<7))) u->raw|=2u; } static void start(esk_esp32_uart *u) { unsigned n,ones=0;uint32_t data; - if(u->active || !u->tx_count || (u->conf0&(1u<<18)) || ((u->conf0&(1u<<15)) && u->cts)) return; - data=u->tx[u->tx_read];u->tx_read=(u->tx_read+1u)%128u;u->tx_count--; + if(u->active || !u->tx_count || u->external_tx_reset || (u->conf0&(1u<<18)) || ((u->conf0&(1u<<15)) && u->cts)) return; + data=storage(u,0)[u->tx_read];u->tx_read=(u->tx_read+1u)%capacity(u,0);u->tx_count--; u->frame.bits=5u+((u->conf0>>2)&3u);data&=(1u<frame.bits)-1u; for(n=0;nframe.bits;n++) ones+=(data>>n)&1u; u->frame.data=data;u->frame.parity_enabled=(u->conf0>>1)&1u; @@ -48,52 +55,60 @@ static unsigned tx_level(const esk_esp32_uart *u) return 1; } void esk_esp32_uart_init(esk_esp32_uart *u) { if(u) memset(u,0,sizeof *u); } -es_status esk_esp32_uart_configure(esk_esp32_uart *u,int on,int rxd,int cts,int dsr) +static es_status uart_configure(esk_esp32_uart *u,int on,int rxd,int cts,int dsr,int coordinated) { if(!u || u->advancing || !binary(on) || !binary(rxd) || !binary(cts) || !binary(dsr)) return ES_EINVAL; + if(u->shared && !coordinated) return ES_EUNSUPPORTED; esk_esp32_uart_init(u);if(!on) return ES_OK; - u->enabled=1;u->conf0=0x0800001c;u->conf1=0x6060;u->divider=0x2b6; + u->enabled=1;u->memconf=0x88;u->conf0=0x0800001c;u->conf1=0x6060;u->divider=0x2b6; u->rxd=(uint32_t)rxd;u->cts=(uint32_t)cts;u->dsr=(uint32_t)dsr;update(u);return ES_OK; } -static int reg_offset(uint32_t address) +es_status esk_esp32_uart_configure(esk_esp32_uart *u,int on,int rxd,int cts,int dsr) +{ return uart_configure(u,on,rxd,cts,dsr,0); } +static int reg_offset(const esk_esp32_uart *u,uint32_t address) { - if(address>=BASE && addressport); + if(address>=base && addressport)) return 0; return -1; } es_status esk_esp32_uart_read(esk_esp32_uart *u,uint32_t address,unsigned bytes,uint32_t *value) { - uint32_t result;int off=reg_offset(address); + uint32_t result;int off; if(!u || !value || u->advancing) return ES_EINVAL; + off=reg_offset(u,address); if(!u->enabled || off<0) return ES_EUNSUPPORTED; if(bytes!=4 || (address&3u)) return ES_EINVAL; switch(off) { case 0: if(!u->rx_count) return ES_EUNSUPPORTED; - result=u->rx[u->rx_read];u->rx_read=(u->rx_read+1u)%128u;u->rx_count--;update(u);break; + result=storage(u,1)[u->rx_read];u->rx_read=(u->rx_read+1u)%capacity(u,1);u->rx_count--;update(u);break; case 4: result=u->raw;break; case 8: result=u->raw&u->ena;break; case 12: result=u->ena;break; case 20: result=u->divider;break; - case 28: result=u->rx_count|(u->tx_count<<16)|(tx_state(u)<<24)|(tx_level(u)<<31)| + case 28: result=(u->rx_count&255u)|((u->tx_count&255u)<<16)|(tx_state(u)<<24)|(tx_level(u)<<31)| (u->rxd<<15)|(u->cts<<14)|(u->dsr<<13)|((u->conf0&0x40u)<<24)|((u->conf0&0x80u)<<22);break; case 32: result=u->conf0;break; case 36: result=u->conf1;break; + case 88: result=u->memconf;break; + case 100: result=(u->rx_count>>8)|((u->tx_count>>8)<<3);break; case 96: result=(u->rx_read<<2)|(u->rx_write<<13);break; default: return ES_EUNSUPPORTED; } *value=result;return ES_OK; } -es_status esk_esp32_uart_write(esk_esp32_uart *u,uint32_t address,unsigned bytes,uint32_t value) +static es_status uart_write(esk_esp32_uart *u,uint32_t address,unsigned bytes,uint32_t value,int coordinated) { - int off=reg_offset(address);uint32_t masked; + int off;uint32_t masked; if(!u || u->advancing) return ES_EINVAL; + off=reg_offset(u,address); if(!u->enabled || off<0) return ES_EUNSUPPORTED; if(bytes!=4 || (address&3u)) return ES_EINVAL; switch(off) { case 0: - if(u->tx_count==128u || (u->conf0&(1u<<18))) return ES_EUNSUPPORTED; - u->tx[u->tx_write]=(uint8_t)(value&255u);u->tx_write=(u->tx_write+1u)%128u;u->tx_count++;start(u);break; + if(u->tx_count==capacity(u,0) || u->external_tx_reset || (u->conf0&(1u<<18))) return ES_EUNSUPPORTED; + storage(u,0)[u->tx_write]=(uint8_t)(value&255u);u->tx_write=(u->tx_write+1u)%capacity(u,0);u->tx_count++;start(u);break; case 12: u->ena=value&IRQ_MASK;break; case 16: u->raw&=~(value&IRQ_MASK);break; case 20: @@ -104,16 +119,33 @@ es_status esk_esp32_uart_write(esk_esp32_uart *u,uint32_t address,unsigned bytes masked=value&0xfffffffu; if((masked&~CONF0_MASK) || ((masked>>4)&3u)==0u || ((masked>>4)&3u)==3u || (u->active && masked!=u->conf0)) return ES_EUNSUPPORTED; + if(u->port==2 && (masked&(1u<<18))) return ES_EUNSUPPORTED; + if(u->port==1 && ((masked|u->conf0)&(1u<<18)) && !coordinated) return ES_EUNSUPPORTED; u->conf0=masked; if(masked&(1u<<18)) u->tx_read=u->tx_write=u->tx_count=0; start(u);break; case 36: if(value&~CONF1_MASK) return ES_EUNSUPPORTED; u->conf1=value;break; + case 88: { + unsigned tx_size,rx_size; + masked=value&UINT32_C(0x7fff87f9); + if(masked&~UINT32_C(0x7e1c07f8)) return ES_EUNSUPPORTED; + tx_size=(masked>>7)&15u;rx_size=(masked>>3)&15u; + if(!tx_size || !rx_size || tx_size>8u-u->port || rx_size>5u-u->port || + (!u->shared && (tx_size!=1 || rx_size!=1))) return ES_EUNSUPPORTED; + if(((masked^u->memconf)&0x780u) && (u->active || u->tx_count)) return ES_EUNSUPPORTED; + if(((masked^u->memconf)&0x78u) && u->rx_count) return ES_EUNSUPPORTED; + if((masked^u->memconf)&0x780u) u->tx_read=u->tx_write=0; + if((masked^u->memconf)&0x78u) u->rx_read=u->rx_write=0; + u->memconf=masked;break; + } default: return ES_EUNSUPPORTED; } update(u);return ES_OK; } +es_status esk_esp32_uart_write(esk_esp32_uart *u,uint32_t address,unsigned bytes,uint32_t value) +{ return uart_write(u,address,bytes,value,0); } es_status esk_esp32_uart_inputs(esk_esp32_uart *u,int rxd,int cts,int dsr) { if(!u || u->advancing || !binary(rxd) || !binary(cts) || !binary(dsr)) return ES_EINVAL; @@ -129,10 +161,10 @@ es_status esk_esp32_uart_receive(esk_esp32_uart *u,uint32_t data,unsigned flags) if((flags&1u) && !(u->conf0&2u)) return ES_EINVAL; u->raw|=(flags&1u)*4u+(flags&2u)*4u; if(!flags || !(u->conf0&(1u<<26))) { - if(u->rx_count==128u) u->raw|=16u; + if(u->rx_count==capacity(u,1)) u->raw|=16u; else { - u->rx[u->rx_write]=(uint8_t)(data&((1u<<(5u+((u->conf0>>2)&3u)))-1u)); - u->rx_write=(u->rx_write+1u)%128u;u->rx_count++; + storage(u,1)[u->rx_write]=(uint8_t)(data&((1u<<(5u+((u->conf0>>2)&3u)))-1u)); + u->rx_write=(u->rx_write+1u)%capacity(u,1);u->rx_count++; } } update(u);return ES_OK; @@ -166,3 +198,49 @@ es_status esk_esp32_uart_irq(const esk_esp32_uart *u,int *asserted) if(!u->enabled) return ES_EUNSUPPORTED; *asserted=(u->raw&u->ena)!=0;return ES_OK; } + +static int bank_busy(const esk_esp32_uart_bank *b) +{ return b->port[0].advancing || b->port[1].advancing || b->port[2].advancing; } +void esk_esp32_uart_bank_init(esk_esp32_uart_bank *b) +{ + unsigned i;if(!b) return; + memset(b,0,sizeof *b); + for(i=0;i<3;i++) { b->port[i].port=i;b->port[i].shared=b->ram; } +} +es_status esk_esp32_uart_bank_configure(esk_esp32_uart_bank *b,unsigned port,int on,int rxd,int cts,int dsr) +{ + es_status st;if(!b || port>2 || bank_busy(b)) return ES_EINVAL; + st=uart_configure(&b->port[port],on,rxd,cts,dsr,1); + if(st!=ES_OK) return st; + b->port[port].port=port;b->port[port].shared=b->ram; + b->port[2].external_tx_reset=(b->port[1].conf0>>18)&1u; + start(&b->port[2]);return ES_OK; +} +static int bank_port(uint32_t address) +{ + unsigned i;for(i=0;i<3;i++) if((address>=apb_base(i) && addressport[port],address,bytes,value); +} +es_status esk_esp32_uart_bank_write(esk_esp32_uart_bank *b,uint32_t address,unsigned bytes,uint32_t value) +{ + int port;es_status st;unsigned reset; + if(!b || bank_busy(b)) return ES_EINVAL; + port=bank_port(address);if(port<0) return ES_EUNSUPPORTED; + if(b->port[(unsigned)port].enabled && (bytes!=4 || (address&3u))) return ES_EINVAL; + if(port==1 && address==apb_base(1)+32u) { + reset=(value>>18)&1u; + if(reset && b->port[2].active) return ES_EUNSUPPORTED; + st=uart_write(&b->port[1],address,bytes,value,1);if(st!=ES_OK) return st; + b->port[2].external_tx_reset=reset; + if(reset) b->port[2].tx_read=b->port[2].tx_write=b->port[2].tx_count=0; + else start(&b->port[2]); + update(&b->port[2]);return ES_OK; + } + return uart_write(&b->port[(unsigned)port],address,bytes,value,0); +} diff --git a/core/mcu/esp32/uart.h b/core/mcu/esp32/uart.h index 15c127e4f..559ed0944 100644 --- a/core/mcu/esp32/uart.h +++ b/core/mcu/esp32/uart.h @@ -3,20 +3,20 @@ #ifndef ESK_ESP32_UART_H #define ESK_ESP32_UART_H #include "embsim/types.h" -/* Opt-in UART0 normal asynchronous profile, fixed 128-byte FIFOs, empty on +/* Opt-in normal asynchronous UART profile, 128-byte FIFOs by default, empty on * explicit host attach. External inputs RXD/CTS/DSR are required, not pin defaults. * TX uses externally advanced selected source-clock ticks, with exact rational * divider accounting (1/32 source-cycle units). No CPU-cycle/wall-time inference. * Framing/divider configuration is immutable during a TX frame; unsupported changes * fail unchanged. 5..8 data bits, parity, 1/1.5 stop supported. STOP=2 workaround, * IRDA, RS485, autobaud, DMA, loopback, inversion, RX flow/software flow, break, - * idle insertion, power/gating/reset domains, UART1/2 and physical pin binding + * idle insertion, power/gating/reset domains and physical pin binding * are absent. RX supplies completed decoded frames/errors, not sampled edges; * RX timeout is an explicit external qualified event, not an invented duration. * RX FIFO reset is deliberately unsupported: SDK documents a silicon issue and * drains through APB instead. TX FIFO reset only while serializer idle. * FIFO aliases APB/AHB 32-bit; empty read/full write fail unchanged rather than - * invent undefined bytes/drops. Unknown MMIO/WO reads fail. IRQ source 34. + * invent undefined bytes/drops. Unknown MMIO/WO reads fail. Bank IRQ sources 34/35/36. * Serialized calls only; callbacks must not re-enter. NULL emit explicitly * discards completed frames. Hold/pad voltage and USB bridge are not modeled. */ typedef struct esk_esp32_uart_frame { @@ -25,11 +25,24 @@ typedef struct esk_esp32_uart_frame { /* Completion offset within the current advance call, in 1/32 source-cycle units. */ typedef void (*esk_esp32_uart_emit)(void *ctx,const esk_esp32_uart_frame *frame,uint64_t offset32); typedef struct esk_esp32_uart { - uint32_t enabled,conf0,conf1,divider,ena,raw,rxd,cts,dsr; + uint32_t enabled,conf0,conf1,divider,ena,raw,rxd,cts,dsr,memconf; + unsigned port,external_tx_reset;uint8_t *shared; uint8_t tx[128],rx[128];unsigned tx_read,tx_write,tx_count,rx_read,rx_write,rx_count; unsigned active,phase,half_bits,advancing;uint64_t remaining; esk_esp32_uart_frame frame; } esk_esp32_uart; +/* UART0/1/2 coordinator: default TX bases 0/128/256, RX bases 384/512/640. + * A shared byte array preserves overlap writes. Only nonwrapping allocations + * within 1024 bytes are qualified; zero sizes, live resize, RAM power-down and + * shared-RAM arbitration/latency remain unsupported. UART1 TX reset affects + * UART2 only with both serializers idle; active-reset cases fail unchanged. + * UART2 has no FIFO reset; RX reset remains unsupported on every port. + * Public per-port write cannot bypass coordinated UART1 reset handling. */ +typedef struct esk_esp32_uart_bank { esk_esp32_uart port[3];uint8_t ram[1024]; } esk_esp32_uart_bank; +void esk_esp32_uart_bank_init(esk_esp32_uart_bank *b); +es_status esk_esp32_uart_bank_configure(esk_esp32_uart_bank *b,unsigned port,int on,int rxd,int cts,int dsr); +es_status esk_esp32_uart_bank_read(esk_esp32_uart_bank *b,uint32_t address,unsigned bytes,uint32_t *value); +es_status esk_esp32_uart_bank_write(esk_esp32_uart_bank *b,uint32_t address,unsigned bytes,uint32_t value); void esk_esp32_uart_init(esk_esp32_uart *u); /* Initialize the standalone object before its first configuration. * on=0 detach; on=1 attach/reset profile with explicit binary input levels. */ diff --git a/docs/STATUS.md b/docs/STATUS.md index 0adf83bc9..005b57b98 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -7,7 +7,16 @@ does not mean the described behaviour exists. ## Original ESP32 dual-core target investigation (2026-11-04) -**Latest UART0 update:** the [normal asynchronous UART0 profile](engineering/esp32-uart0.md) +**Latest UART-bank update:** the [normal UART0/1/2 bank](engineering/esp32-uart-bank.md) +now shares one 1024-byte FIFO RAM, supports bounded allocations/overwrites and +extended counts/thresholds, and coordinates idle UART1→UART2 TX reset. 271,883 +new native assertions, 4,096 shared-RAM referee turns and 384 compiled C calls +with twelve unchanged SDK helpers pass. Native ROM with all three explicit UART +fixtures reaches 6,790 retirements, then an unmapped instruction-ROM fetch at +0x40065c18. Extended ROM mapping, advanced UART modes, full physical clock/pin/USB +integration and complete SDK boot remain unqualified. + +**Prior UART0 update:** the [normal asynchronous UART0 profile](engineering/esp32-uart0.md) now supports fixed TX/RX FIFOs, explicit source-clock framing, CTS gating, decoded RX/error input and interrupt source 34 on both native core views. 915,199 native assertions, 316,077 independent bit/frame comparisons and 128 compiled C calls diff --git a/docs/engineering/esp32-boot-verification.md b/docs/engineering/esp32-boot-verification.md index ef4b256ae..ec2fb99dc 100644 --- a/docs/engineering/esp32-boot-verification.md +++ b/docs/engineering/esp32-boot-verification.md @@ -3,8 +3,8 @@ **Result on 2026-11-05: the owner's board reports ESP32-D0WD-V3 revision 3.1, JEDEC C4 60 16 and 4 MB flash. Giantec's GT25Q32A-U datasheet matches that ID; exact package/ordering suffix remains unconfirmed. The pinned SDK example boots -in Espressif QEMU. Native EmbSIM reaches UART1 initialization after 6,737 retired -instructions with explicit synthetic eFuse/RTC, active GPIO and UART0 fixtures; without an eFuse profile, +in Espressif QEMU. Native EmbSIM reaches an instruction-ROM mapping boundary after 6,790 retired +instructions with explicit synthetic eFuse/RTC, active GPIO and UART-bank fixtures; without an eFuse profile, the baseline stops at an eFuse read after seven.** Neither result establishes successful native boot. @@ -114,7 +114,17 @@ configuration or printed SDK capacity must not substitute for hardware identity. ## Native EmbSIM result: blocked -**Latest UART0 follow-up:** [Normal UART0](esp32-uart0.md) adds native FIFOs, +**Latest UART-bank follow-up:** [UART0/1/2 and shared RAM](esp32-uart-bank.md) +passes the previous UART1 interrupt clear. With `--uart-bank-active-fixture`, +PRO retires 6,790 instructions / 6,791 attempts, then fails fetch at 0x40065c18 +from a real CALL8 at 0x40007729. The current fabric exposes only the first 384 KiB +instruction-ROM window; the SDK declares the mask range through 0x40070000. +Extended mapping/image layout remains unqualified, with no substituted RAM or PC +shortcut. The [bank evidence](../../tests/vendor/xtensa_integer/evidence/boot-with-uart-bank-2026-11-05.json) +retains boot unverified. This fixture uses explicit empty default UART0/1/2 +allocations and RXD=1/CTS=0/DSR=0; it advances no serial ticks. + +**Prior UART0 follow-up:** [Normal UART0](esp32-uart0.md) adds native FIFOs, source-clock framing and interrupt handling. With `--uart0-active-fixture` and all prior explicit fixtures, the probe retires 6,737 instructions / 6,738 attempts, then blocks on UART1 INT_CLR write 0x3ff50010, PC 0x40009013, opcode 0x68a2. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 92fb2c170..a98ca6cdd 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -44,6 +44,10 @@ board wiring and electrical loading remain unqualified. interrupt delivery; its explicit fixture reaches 6,737 instructions before UART1 INT_CLR write 0x3ff50010/PC 0x40009013. Other UARTs, advanced modes and physical pin/clock/USB behavior remain absent. +[UART bank/shared RAM](esp32-uart-bank.md) now extends normal mode to UART1/2, +bounded overlapping allocations and idle coupled reset. The all-port fixture +reaches 6,790 retirements, then instruction-ROM fetch at 0x40065c18 outside the +currently exposed fabric window. Extended ROM mapping remains a separate task. [Instruction timing](esp32-instruction-timing.md) still requires physical evidence; a buildable capture firmware is prepared without claiming calibrated latency. It is not the owner's fuse dump and does not establish full boot. The [Giantec storage subset](esp32-giantec-profile.md) diff --git a/docs/engineering/esp32-uart-bank.md b/docs/engineering/esp32-uart-bank.md new file mode 100644 index 000000000..95b27448e --- /dev/null +++ b/docs/engineering/esp32-uart-bank.md @@ -0,0 +1,160 @@ +# Original ESP32 UART bank and shared FIFO RAM + +**Implemented: normal asynchronous UART0/1/2, shared FIFO storage, bounded +allocation changes, extended counts/thresholds and idle-only UART1→UART2 TX +reset coupling. This does not complete every UART mode, clock/pin/USB binding, +reset domain, physical timing or ROM/SDK boot.** + +## Hardware facts and observable contract + +The [UART0 contract](esp32-uart0.md) documents framing, CTS gating, decoded RX, +error handling, externally advanced fractional source clocks and interrupt +latches. This follow-up reuses those qualified operations for all three ports; +it adds a coordinator for shared memory/reset behavior rather than independent +FIFO copies. Its primary references are ESP32 TRM v5.9 chapter 19, especially +Figure 19.3-2, and the pinned ESP-IDF v4.4.7 UART register/LL headers. + +| Port | APB registers | AHB FIFO | Matrix source | Default TX RAM offset | Default RX RAM offset | +| --- | --- | --- | --- | --- | --- | +| UART0 | 0x3ff40000 | 0x60000000 | 34 | 0 | 384 | +| UART1 | 0x3ff50000 | 0x60010000 | 35 | 128 | 512 | +| UART2 | 0x3ff6e000 | 0x6002e000 | 36 | 256 | 640 | + +Offsets are bytes in the shared 1024-byte RAM. Default capacities are 128 bytes. +The bank owns one byte array. Each FIFO has independent cursors/counts; extending +one into a peer's allocation really allows writes to overwrite bytes still queued +by that peer. A serializer captures a byte when removing it from its FIFO, so +later RAM writes do not rewrite a frame already in flight. RAM is not zeroed by +FIFO reset or host attach. Initial undefined RAM bytes are not readable through +an empty FIFO and no guessed raw-RAM MMIO interface is exposed. + +### Allocation and counters + +`MEM_CONF` exposes TX_SIZE/RX_SIZE, high TX-empty/RX-full thresholds and high RX +timeout configuration. Sizes count 128-byte blocks at the documented default +bases. Supported allocations stay within the end of physical RAM: TX sizes +1..(8−port), RX sizes 1..(5−port). Overlap is supported; end-of-RAM wrapping, +zero-size behavior and 9..15-block aliasing are unqualified and fail unchanged. +RAM power-down, flow-control extensions and unsupported features also fail. + +Changing a TX allocation requires its queue empty and serializer idle; changing +an RX allocation requires its queue empty. Changed allocation cursors restart at +zero as an explicit empty-queue fixture policy; live migration, cursor behavior +across nonempty resize and arbitration latency are not guessed. Threshold-only +changes remain available while queues contain data. Reserved bits do not enable +features or allocate memory. + +The low eight count bits appear in STATUS; the upper three bits appear in +MEM_CNT_STATUS. MEM_RX_STATUS projects relative read/write cursors for the +selected allocation. Absolute bus pipeline behavior and silicon FIFO-count +errata are not qualified. RX-full and TX-empty thresholds concatenate their +three MEM_CONF high bits with the CONF1 low seven bits; raw threshold events +remain latched until W1C and reassert while their condition remains true. + +The SDK default FIFO-length/drain helpers are qualified with one-block +allocations. Its RX length helper contains a hardcoded modulo-128 calculation; +these tests do not claim it handles expanded FIFOs or replace its code with a +host approximation. Expanded counts, wrap and bytes are checked separately by +native tests and an independent shared-RAM referee. + +### Reset coupling and failure boundaries + +The SDK states that UART1 TX FIFO reset also resets UART2's TX FIFO, and avoids +that helper on both ports. The bank supports the corresponding explicit CONF0 +TX-reset control when **both serializers are idle**: UART1 and UART2 TX cursors +and counts clear, UART0 remains intact, and stored RAM bytes remain unchanged. +While held, the shared reset blocks UART2 queue writes/start; releasing UART1's +reset releases that input. UART2's own CONF0 reset bit is unsupported because the +TRM states that UART2 has no FIFO-reset register. + +A reset request while either affected serializer is active fails without partial +clearing, frame abort, fabricated TX_DONE or peer changes. Invalid configuration +combined with reset also fails without touching peer/RAM state. UART0 TX reset +remains local and idle-only. RX reset is unsupported on every port: the SDK +warns that the reset bit is unreliable, so the unchanged RX drain helper is +exercised instead. Cross-port active-reset glitches, exact reset synchronizers +and the RX-reset erratum's undocumented effects are not modeled. + +## Host and machine integration + +`esk_esp32_uart_bank_configure` and `esk_esp32_machine_uart_port_configure` +select an explicit port and require RXD/CTS/DSR levels. They are fixture attach +operations, not inferred GPIO, board netlists or SoC reset producers. Each port +starts with empty default allocations; attaching one does not erase common RAM +or another port's queue. The standalone configure API rejects bank-owned objects +so it cannot accidentally discard their coordinator binding. + +The machine provides port-selected input, decoded receive, qualified timeout and +source-clock advance APIs. Existing UART0 APIs remain compatibility wrappers. +Both CPU bus views share the bank. Sources 34/35/36 are exclusively owned while +their respective ports are attached; external injection into an owned source +fails, and detach clears/releases only that source. Sources mapped to the same +CPU input are OR-combined, and clearing one UART does not dismiss another's IRQ. +Firmware changes route after instruction commit. CPU-only reset retains bank +state; full peripheral/global/power-reset domains remain unqualified. + +Source-clock ticks are explicit per-port inputs. There is no assumed APB/ref +frequency, instruction-to-time conversion or implicit advancement of other +ports. The caller supplies each selected clock's events; physical clock-tree and +pin/electrical integration remain absent. Completion offsets remain in 1/32 +source-cycle units within the current advance call. All operations are serialized; +callbacks must not re-enter. Bank MMIO/configuration and machine APIs reject +calls during advancement. Direct per-port writes cannot bypass shared reset +coordination; use bank MMIO for UART1 reset changes. + +## Full implementation and remaining omissions + +Normal-mode framing, CTS, received decoded frames/errors, FIFO state, thresholds, +interrupts and explicit time inputs are observable. Internal independent cursors +plus one RAM array implement those observations without pretending to model RAM +arbitration cycles. The implementation remains original C99. + +IRDA, RS485, the two-stop-bit workaround, inversion, loopback, autobaud, break, +idle insertion, AT commands, RX/software flow control, DMA/UHCI, wakeup, RAM power +state, end-wrapping allocation, live resize, active resets, waveform-sampled RX, +physical pin loading/USB bridge and calibrated CPU/SoC timing remain unsupported. +Enabling an omitted feature fails; no blanket MMIO success/zero fallback is added. + +## Proof and reproduction + +- **271,883 native bank assertions** cover all supported nonwrapping size pairs + on all ports, full/empty/wrap/overflow, extended counts, relative cursors, real + cross-port TX/RX RAM overwrites, preserved RAM, reset hold/coupling/rejection, + invalid operation rollback, source ownership and OR-combined CPU inputs. +- The previous **915,199 UART0 assertions** still pass. The default UART0 vendor + command still produces its prior 128 C calls / 44,032 instructions. +- The all-port runner checks **316,077 independent bit/frame/completion-offset + observations**, plus **4,096 independent shared-RAM turns**. The latter uses + a Python byte array, independently tracked cursors/counts/IRQ latches and the + TRM's allocation bases; it compares counts, data, pointers and raw/masked IRQs + after writes, reads, resizes, clears, resets and frame completion. It observes + overwritten queued bytes rather than caching idealized FIFO payloads. +- **384 actual ESP32 C interactive calls / 160,128 retired instructions** execute + **twelve unchanged SDK helper bodies** across every port on both native CPUs. + Firmware writes through real AHB addresses, reads through APB, checks FIFO and + interrupt helpers, then drains three additional externally received bytes + using `uart_ll_rxfifo_rst`. A queued UART2 byte survives the SDK's intentional + UART1 TX-reset-helper no-op. These are native simulator tests, not hardware + captures, physical UART timing proof or successful SDK boot. + +Use the [UART0 reproduction command](esp32-uart0.md#evidence-and-reproduction) +with **`--all-ports`** to select the bank tests. External references retain the +same SDK/TRM hashes and licences. No vendor runtime or ROM bytes are bundled. + +## Native boot boundary + +Add **`--uart-bank-active-fixture`** to the ROM diagnostic with the existing +explicit eFuse/RTC/GPIO fixtures, instead of `--uart0-active-fixture`. Each port +has empty default allocations and explicit RXD=1/CTS=0/DSR=0. No serial ticks or +physical board pinmux are inferred. + +Native ROM now retires **6,790 instructions / 6,791 attempts**, passing the +previous UART1 interrupt-clear blocker. It then fails instruction fetch at +**0x40065c18**, entered by the real CALL8 at 0x40007729. The fabric currently +exposes the first 384 KiB instruction-ROM range, while the SDK declares a mask-ROM +range through 0x40070000 and provides some symbols above 0x40060000. Qualifying +that extended mapping and the external ROM image layout is a separate boot task; +this milestone does not substitute RAM, zero fill or a host PC shortcut there. +[Hash-recorded evidence](../../tests/vendor/xtensa_integer/evidence/boot-with-uart-bank-2026-11-05.json) +still reports `boot_verified: false`. The previous UART0-only 6,737-instruction +boundary remains reproducible by its original fixture selection. diff --git a/docs/engineering/esp32-uart0.md b/docs/engineering/esp32-uart0.md index 8bd878364..4f641bbe0 100644 --- a/docs/engineering/esp32-uart0.md +++ b/docs/engineering/esp32-uart0.md @@ -3,7 +3,11 @@ **Implemented: opt-in UART0 normal asynchronous transmit/receive FIFOs, explicit source-clock advancement, framing, CTS gating, interrupt latches and native APB/AHB firmware access. This is not a complete UART, pin binding, USB bridge, -physical clock calibration or successful SDK boot. UART1/2 remain unsupported.** +physical clock calibration or successful SDK boot.** + +The [UART-bank follow-up](esp32-uart-bank.md) now extends this normal-mode +profile to UART1/2 with shared RAM and bounded reset coupling. This page preserves +the original UART0 contract and its reproducible baseline evidence. ## Hardware and implementation boundary @@ -16,8 +20,7 @@ threshold, modem-change and completion interrupts have independent raw latches, software enables and write-one clear controls. This original C99 implementation has no copied vendor runtime. It supports the -fixed UART0 allocation at APB 0x3ff40000 and FIFO alias 0x60000000. UART1/2, -allocation changes/shared-RAM interference, DPORT peripheral reset/clock gates, +fixed UART0 allocation at APB 0x3ff40000 and FIFO alias 0x60000000. DPORT peripheral reset/clock gates, DMA/UHCI, IRDA, RS485, autobaud, inversion, loopback, RX flow/software flow, break detection/transmission, idle insertion, AT commands, wakeup and electrical pad/USB behavior remain absent. Unsupported register/features/accesses return an @@ -27,12 +30,13 @@ The pinned SDK documents hardware issues that constrain this profile: - `uart_ll_rxfifo_rst` drains through APB because the hardware RX reset bit is unreliable. Setting that bit is unsupported here. The SDK helper itself also - inspects RX memory pointers; its complete reset procedure is not yet qualified. + inspects RX memory pointers; the bank follow-up now qualifies this drain helper + with default allocations on all three ports. - `uart_ll_set_stop_bits` uses RS485 `dl1_en` for the two-stop-bit workaround. STOP encoding 3 and that workaround remain unsupported. One and 1.5 stop bits are implemented; invalid encoding zero fails. -- UART1 TX reset also affects UART2. Neither port is inferred from UART0 or - exposed through an invented independent reset. +- UART1 TX reset also affects UART2. The bank follow-up implements an idle-only + coupled reset; active-reset behavior remains unqualified. ## Explicit fixture and externally visible behavior diff --git a/tests/unit/test_esp32_uart_bank.c b/tests/unit/test_esp32_uart_bank.c new file mode 100644 index 000000000..d3608ddd6 --- /dev/null +++ b/tests/unit/test_esp32_uart_bank.c @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/machine.h" +static uint32_t base(unsigned port) +{ return port==0 ? UINT32_C(0x3ff40000) : port==1 ? UINT32_C(0x3ff50000) : UINT32_C(0x3ff6e000); } +static uint32_t fifo(unsigned port) +{ return port==0 ? UINT32_C(0x60000000) : port==1 ? UINT32_C(0x60010000) : UINT32_C(0x6002e000); } +static uint32_t rd(esk_esp32_uart_bank *b,unsigned port,unsigned offset) +{ uint32_t v=0;CHECK_EQ(esk_esp32_uart_bank_read(b,base(port)+offset,4,&v),ES_OK);return v; } +static void wr(esk_esp32_uart_bank *b,unsigned port,unsigned offset,uint32_t v) +{ CHECK_EQ(esk_esp32_uart_bank_write(b,base(port)+offset,4,v),ES_OK); } +typedef struct burst { unsigned count,seed;uint64_t last; } burst; +static void emit(void *ctx,const esk_esp32_uart_frame *f,uint64_t offset32) +{ + burst *c=(burst *)ctx;CHECK_EQ(f->data,(c->seed+c->count)&255u);CHECK_EQ(f->bits,8); + CHECK(offset32>c->last);c->last=offset32;c->count++; +} +static void all_ports_and_bounded_allocations(void) +{ + esk_esp32_uart_bank b,before;unsigned port,tx,rx,n,txcap,rxcap;burst c;uint32_t status,counts; + for(port=0;port<3;port++) for(tx=1;tx<=8u-port;tx++) for(rx=1;rx<=5u-port;rx++) { + esk_esp32_uart_bank_init(&b);CHECK_EQ(esk_esp32_uart_bank_configure(&b,port,1,1,1,0),ES_OK); + wr(&b,port,20,2);wr(&b,port,32,0x0800801c);wr(&b,port,88,(tx<<7)|(rx<<3)); + CHECK_EQ(rd(&b,port,88),(tx<<7)|(rx<<3));txcap=tx*128u;rxcap=rx*128u; + for(n=0;n>16)&255u)|(((counts>>3)&7u)<<8),txcap); + before=b;CHECK_EQ(esk_esp32_uart_bank_write(&b,fifo(port),4,0),ES_EUNSUPPORTED); + CHECK_EQ(memcmp(&b,&before,sizeof b),0); + CHECK_EQ(esk_esp32_uart_inputs(&b.port[port],1,0,0),ES_OK);memset(&c,0,sizeof c); + CHECK_EQ(esk_esp32_uart_advance(&b.port[port],1000000,emit,&c),ES_OK);CHECK_EQ(c.count,txcap); + CHECK_EQ(c.last,(uint64_t)txcap*640u); + for(n=0;nirq.enabled=1; + for(p=0;p<3;p++) CHECK_EQ(bus.write(bus.ctx,0x3ff00104u+cpu*0x114u+(34u+p)*4u,4,19),ES_OK); + } + for(p=0;p<3;p++) { + CHECK_EQ(esk_esp32_machine_uart_port_configure(m,p,1,1,0,0),ES_OK); + CHECK_EQ(esk_esp32_machine_source(m,34u+p,1),ES_EUNSUPPORTED); + CHECK_EQ(bus.write(bus.ctx,base(p)+36u,4,0),ES_OK);CHECK_EQ(bus.write(bus.ctx,base(p)+12u,4,1),ES_OK); + CHECK_EQ(esk_esp32_machine_uart_port_receive(m,p,0x40u+p,0),ES_OK); + } + for(p=0;p<3;p++) { + for(cpu=0;cpu<2;cpu++) CHECK(w[cpu]->irq.lines&(1u<<19)); + CHECK_EQ(bus.read(bus.ctx,base(p),4,&v),ES_OK);CHECK_EQ(v,0x40u+p); + CHECK_EQ(bus.write(bus.ctx,base(p)+16u,4,1),ES_OK); + } + for(cpu=0;cpu<2;cpu++) CHECK_EQ(w[cpu]->irq.lines&(1u<<19),0); + CHECK_EQ(esk_esp32_machine_uart_port_receive(m,3,0,0),ES_EINVAL); + CHECK_EQ(esk_esp32_machine_uart_port_advance(m,3,0,NULL,NULL),ES_EINVAL); + for(p=0;p<3;p++) { + CHECK_EQ(esk_esp32_machine_uart_port_configure(m,p,0,0,0,0),ES_OK); + CHECK_EQ(esk_esp32_machine_source(m,34u+p,1),ES_OK); + } + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +TEST_MAIN_BEGIN + (void)t_str_eq;RUN(all_ports_and_bounded_allocations);RUN(shared_overlap_and_preserved_ram); + RUN(coupled_reset_and_unchanged_failures);RUN(high_thresholds_and_allocation_rejection);RUN(machine_interrupt_or_and_independent_ports); +TEST_MAIN_END diff --git a/tests/vendor/README.md b/tests/vendor/README.md index 56f5bf3ab..b15804af6 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -246,3 +246,9 @@ observations and runs 128 interactive compiled C calls using eleven unchanged ESP-IDF UART helpers on both native buses. Source clocks and decoded RX frames are explicit host inputs; UART1/2, advanced modes and physical pin/clock/USB behavior remain unqualified. See the [UART0 contract and reproduction](../../docs/engineering/esp32-uart0.md). + +`xtensa_integer/run_uart.py --all-ports` additionally checks 4,096 independent +shared-RAM turns and runs 384 compiled C calls / twelve unchanged SDK helpers +across UART0/1/2 on both native cores. It verifies overlapping FIFO bytes, +extended counts/thresholds and bounded shared reset, and exercises the SDK RX +drain helper. [Bank contract and omissions](../../docs/engineering/esp32-uart-bank.md). diff --git a/tests/vendor/xtensa_integer/evidence/boot-with-uart-bank-2026-11-05.json b/tests/vendor/xtensa_integer/evidence/boot-with-uart-bank-2026-11-05.json new file mode 100644 index 000000000..cdce4c536 --- /dev/null +++ b/tests/vendor/xtensa_integer/evidence/boot-with-uart-bank-2026-11-05.json @@ -0,0 +1,95 @@ +{ + "boot_verified": false, + "outcome": "blocked", + "core": "PRO only", + "rom_sha256": "9115bbcd03da9f54d3baca5ad94d588a43da54ed332347137ba3dc9010df258b", + "flash_sha256": "52fcca3c31ed835d45e22826d42dd7a7db0c867171fa330f9c56b0a81df7bee7", + "reset_vector": "0x40000400", + "memctl_profile": "bit 0 plus deterministic 256-byte loop-buffer fixture; not physical fetch/timing qualification", + "region_profile": "original ESP32 eight ITLB/DTLB identity regions, reset attribute 2, one ring, no Xtensa cache", + "prid_fixture": "0x0000cdcd", + "prid_reference": "ESP-IDF v4.4.7 xtensa_context.h documents PRO=0xcdcd, APP=0xabab; not a hardware readout", + "rom_tail_policy": "zero-fill absent DROM tail to match QEMU region setup, not silicon contents", + "flash_profile": "immutable SPI1 fixture, not a physical flash identity", + "uart_bank_fixture": "normal UART0/1/2, default disjoint shared FIFO allocations, empty FIFOs, explicit RXD=1 CTS=0 DSR=0 per port, no serial-clock advancement or board pinmux", + "gpio_fixture": "no routed pads; defined active-domain DIG_ISO defaults and hold controls only, no isolation/power emulation", + "rtc_fixture": "power-on cause 1 per core, vector selects 1, eight explicit zero retention words; not physical-owner RTC state", + "efuse_fixture_sha256": "2d8192ea7bdf3510bc117b4ea4b833a94b2ff2ceff252ce1610c297ed633d5bb", + "efuse_completion_fixture": "one external completion opportunity every 64 instruction attempts; not physical timing", + "status": -1, + "event": 0, + "pc": "0x40065c18", + "opcode": "0x0", + "address": "0x40065c18", + "fault_kind": 3, + "attempts": 6791, + "region_readback": [ + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ], + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ] + ], + "region_access_attributes": [ + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ], + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ] + ], + "efuse_completions": 3, + "retired_before_stop": 6790, + "trace_tail": [ + "0x4000bf54", + "0x4000bf56", + "0x4000bf58", + "0x4000bf5b", + "0x4000bf67", + "0x4000bf6a", + "0x4000bf6c", + "0x4000bf6f", + "0x400067f8", + "0x40009026", + "0x40007729", + "0x40065c18" + ], + "next_blocker": "Instruction fetch at 0x40065c18, reached by CALL8 at 0x40007729; extended mask-ROM mapping remains unqualified, native SDK boot unverified", + "fixture_note": "Explicit UART0/1/2 default disjoint FIFO allocations, empty queues, RXD=1 CTS=0 DSR=0 per port; no serial-clock advancement or board pinmux inference", + "sources_sha256": { + "core/mcu/esp32/uart.c": "97c5de74624ac6b533430626886f44dc2a5c4ec1d6b4d119bd634310f8a92249", + "core/mcu/esp32/uart.h": "3fee5f2b09b477285a9e4149737e8e09e2206ea023ca6d2d11ecccb7d981bccc", + "core/mcu/esp32/machine.c": "a1e23069bc13c2c3b394737ee175e65d4dc2a7d5f2b90a080138f8b5c91f968c", + "core/mcu/esp32/machine.h": "dba5dc449d3f0898f8c847977f31b5b6e6e44ff49448e8c2efe4998fa689587c", + "tests/vendor/xtensa_integer/probe_rom_boot.py": "0f9eccb586e2eaeaf065e4f6f56c21b55a1bf3bb33f9b33b322fbef56c0ac1d4" + } +} diff --git a/tests/vendor/xtensa_integer/probe_rom_boot.py b/tests/vendor/xtensa_integer/probe_rom_boot.py index c8285744e..fdf5beb11 100644 --- a/tests/vendor/xtensa_integer/probe_rom_boot.py +++ b/tests/vendor/xtensa_integer/probe_rom_boot.py @@ -28,7 +28,9 @@ def main(): ap.add_argument('--rom',type=Path,required=True);ap.add_argument('--flash',type=Path,required=True) ap.add_argument('--efuse',type=Path,help='explicit JSON list of 31 raw None-coded fuse words; not inferred board defaults') ap.add_argument('--efuse-completion-period',type=int,default=64,help='external fixture event every N instruction attempts; not physical timing') - ap.add_argument('--uart0-active-fixture',action='store_true',help='normal UART0 fixture, explicit RXD=1 CTS=0 DSR=0, empty FIFOs, no serial ticks; not board pinmux or other UARTs') + uart_fixtures=ap.add_mutually_exclusive_group() + uart_fixtures.add_argument('--uart-bank-active-fixture',action='store_true',help='normal UART0/1/2 fixtures, explicit RXD=1 CTS=0 DSR=0, empty FIFOs, no serial ticks; not board pinmux') + uart_fixtures.add_argument('--uart0-active-fixture',action='store_true',help='normal UART0 fixture, explicit RXD=1 CTS=0 DSR=0, empty FIFOs, no serial ticks; not board pinmux or other UARTs') ap.add_argument('--gpio-active-fixture',action='store_true',help='explicit no-routed-pads software-GPIO/DIG_ISO hold fixture; isolation/power transitions unsupported') ap.add_argument('--rtc-power-on-fixture',action='store_true',help='explicit synthetic power-on reasons/vector selects and eight zero retention words; not owner RTC state') ap.add_argument('--prid',type=lambda value:int(value,0),default=0xcdcd,help='PRO PRID; SDK documents 0xcdcd; 0 reproduces historical invalid-ID fixture') @@ -61,6 +63,7 @@ def bind(name,result,*params): spi=bind('esk_esp32_fabric_spi1_configure',C.c_int,pointer,C.c_int) core=bind('esk_esp32_machine_core',C.POINTER(Windows),pointer,C.c_uint) uart=bind('esk_esp32_machine_uart_configure',C.c_int,pointer,C.c_int,C.c_int,C.c_int,C.c_int) + uart_port=bind('esk_esp32_machine_uart_port_configure',C.c_int,pointer,C.c_uint,C.c_int,C.c_int,C.c_int,C.c_int) gpio=bind('esk_esp32_machine_gpio_configure',C.c_int,pointer,C.POINTER(C.c_uint32),C.c_size_t) rtc=bind('esk_esp32_machine_rtc_configure',C.c_int,pointer,C.c_uint32,C.POINTER(C.c_uint32),C.c_size_t) memctl=bind('esk_xtensa_memctl_configure',C.c_int,C.POINTER(Windows)) @@ -80,6 +83,9 @@ def bind(name,result,*params): 'flash_profile':'immutable SPI1 fixture, not a physical flash identity'} try: ram=fabric(machine) + if args.uart_bank_active_fixture: + for port in range(3):assert uart_port(machine,port,1,1,0,0)==0 + report['uart_bank_fixture']='normal UART0/1/2, default disjoint shared FIFO allocations, empty FIFOs, explicit RXD=1 CTS=0 DSR=0 per port, no serial-clock advancement or board pinmux' if args.uart0_active_fixture: assert uart(machine,1,1,0,0)==0 report['uart0_fixture']='normal UART0, empty FIFOs, explicit RXD=1 CTS=0 DSR=0, no serial-clock advancement, no UART1/2 or board pinmux' diff --git a/tests/vendor/xtensa_integer/run_uart.py b/tests/vendor/xtensa_integer/run_uart.py index 066916402..ef0f83ec4 100644 --- a/tests/vendor/xtensa_integer/run_uart.py +++ b/tests/vendor/xtensa_integer/run_uart.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 # Copyright 2026 Junior Deogracias -"""UART0 bitstream referee and unchanged ESP-IDF helpers on native core buses. +"""UART bitstream/shared-RAM referee and unchanged ESP-IDF helpers on native core buses. Source-clock ticks are explicit fixture input, not CPU instruction timing. Decoded RX frames are external inputs; no waveform/pin/USB/board qualification. @@ -23,18 +23,93 @@ class Frame(C.Structure): _fields_=[(n,C.c_uint32) for n in ['data','bits','parity_enabled','parity','stop_half_bits','divider16','apb']] +def shared_ram_oracle(bus,m,attach,receive,inputs,advance,emit,frames,times,addr,rng): + """Byte-address referee: shared bytes, independent cursors, no FIFO value cache.""" + ram=bytearray(1024);bases=[[0,128,256],[384,512,640]] + capacities=[[384,256,128],[384,256,128]] + heads=[[0]*3 for _ in range(2)];tails=[[0]*3 for _ in range(2)];counts=[[0]*3 for _ in range(2)] + raw=[2]*3;ena=[0]*3;high=[[0]*3 for _ in range(2)] + def write(p,name,data):assert bus.write(bus.ctx,addr(name,p),4,data)==0,(p,name,hex(data)) + def read(p,name): + value=C.c_uint32(0xdeadbeef);assert bus.read(bus.ctx,addr(name,p),4,C.byref(value))==0 + return value.value + for p in range(3): + assert attach(m,p,1,1,1,0)==0;write(p,'CONF0',0x0800801c);write(p,'CLKDIV',2) + write(p,'MEM_CONF',(capacities[0][p]//128<<7)|(capacities[1][p]//128<<3)) + for turn in range(4096): + p=rng.randrange(3);op=rng.randrange(8);data=rng.randrange(256) + if op==0: + assert receive(m,p,data,0)==0 + if counts[1][p]==capacities[1][p]:raw[p]|=16 + else: + ram[bases[1][p]+tails[1][p]]=data + tails[1][p]=(tails[1][p]+1)%capacities[1][p];counts[1][p]+=1 + elif op==1: + if counts[1][p]: + assert read(p,'FIFO')==ram[bases[1][p]+heads[1][p]],('shared RX',turn,p) + heads[1][p]=(heads[1][p]+1)%capacities[1][p];counts[1][p]-=1 + else: + value=C.c_uint32(0xdeadbeef) + assert bus.read(bus.ctx,addr('FIFO',p),4,C.byref(value))!=0 and value.value==0xdeadbeef + elif op==2: + result=bus.write(bus.ctx,addr('FIFO',p),4,data) + if counts[0][p]==capacities[0][p]:assert result!=0 + else: + assert result==0 + ram[bases[0][p]+tails[0][p]]=data + tails[0][p]=(tails[0][p]+1)%capacities[0][p];counts[0][p]+=1 + elif op==3: + wanted=[ram[bases[0][p]+(heads[0][p]+n)%capacities[0][p]] for n in range(counts[0][p])] + frames.clear();times.clear();assert inputs(m,p,1,0,0)==0 + assert advance(m,p,1000000,emit,None)==0 + assert [f[0] for f in frames]==wanted,('shared TX',turn,p) + assert times==[(n+1)*640 for n in range(len(wanted))] + if wanted:raw[p]|=0x4000 + raw[p]|=64;heads[0][p]=tails[0][p];counts[0][p]=0 + assert inputs(m,p,1,1,0)==0 + elif op==4: + for kind,maximum in [(0,8-p),(1,5-p)]: + size=(rng.randrange(1,maximum+1)*128) if not counts[kind][p] else capacities[kind][p] + if size!=capacities[kind][p]:heads[kind][p]=tails[kind][p]=0 + capacities[kind][p]=size;high[kind][p]=rng.randrange(8) + write(p,'MEM_CONF',(capacities[0][p]//128<<7)|(capacities[1][p]//128<<3)|(high[0][p]<<28)|(high[1][p]<<25)) + elif op==5: + result=bus.write(bus.ctx,addr('CONF0',p),4,0x0804801c) + if p==2:assert result!=0 + else: + assert result==0 + for q in ([0] if p==0 else [1,2]):heads[0][q]=tails[0][q]=counts[0][q]=0 + write(p,'CONF0',0x0800801c) + elif op==6: + mask=rng.getrandbits(19);write(p,'INT_CLR',mask);raw[p]&=~mask + else: + ena[p]=rng.getrandbits(19);write(p,'INT_ENA',ena[p]) + for q in range(3): + if counts[1][q]>(96|(high[1][q]<<7)):raw[q]|=1 + if counts[0][q]<(96|(high[0][q]<<7)):raw[q]|=2 + status=read(q,'STATUS');mem=read(q,'MEM_CNT_STATUS') + assert ((status&255)|((mem&7)<<8))==counts[1][q],('RX count',turn,q) + assert (((status>>16)&255)|(((mem>>3)&7)<<8))==counts[0][q],('TX count',turn,q) + assert read(q,'MEM_RX_STATUS')==(heads[1][q]<<2)|(tails[1][q]<<13),('RX cursors',turn,q) + assert read(q,'INT_RAW')==raw[q],('raw',turn,q,hex(read(q,'INT_RAW')),hex(raw[q])) + assert read(q,'INT_ST')==raw[q]&ena[q] + return 4096 + + def main(): ap=argparse.ArgumentParser(description=__doc__) for n in ['uart-reg','uart-ll','uart-struct','misc']:ap.add_argument('--'+n,type=Path,required=True) for n in ['compiler','objcopy','linker','nm']:ap.add_argument('--'+n,required=True) + ap.add_argument('--all-ports',action='store_true',help='qualify UART0/1/2, shared RAM/allocations/reset and RX drain helper') ap.add_argument('--cc',default='cc');args=ap.parse_args();rng=random.Random(0x4000900b) reg=read_header(args.uart_reg,'29d5e65d1dd560a339eb6057a93a1c8e2bcc90d730c82a8506fd894bd4869922') ll=read_header(args.uart_ll,'b98b4846e92480a24b6710b4dcd025a9c6951e4be37a95ce1fcd0c89df20dda5') struct=read_header(args.uart_struct,'4438b0d2289fa7acc417a6df9a5e4a024bcfe7932396955ad8594a907988be91') misc=read_header(args.misc,'552e2d4ce5be7d93d228eb3b48bd959d0485290a7476ce5a7de438fe7ab9e152') def field(name):return int(re.search(r'#define UART_'+name+r'_S\s+(\d+)',reg)[1]) - def addr(name):return 0x3ff40000+int(re.search(r'#define UART_'+name+r'_REG\(i\)\s+\(REG_UART_BASE\(i\)\s*\+\s*(0x[0-9A-Fa-f]+)\)',reg)[1],16) - names=['ena_intr_mask','disable_intr_mask','get_intraw_mask','get_intsts_mask','clr_intsts_mask','get_intr_ena_status','read_rxfifo','write_txfifo','get_rxfifo_len','get_txfifo_len','txfifo_rst'] + bases=[0x3ff40000,0x3ff50000,0x3ff6e000] + def addr(name,port=0):return bases[port]+int(re.search(r'#define UART_'+name+r'_REG\(i\)\s+\(REG_UART_BASE\(i\)\s*\+\s*(0x[0-9A-Fa-f]+)\)',reg)[1],16) + names=['ena_intr_mask','disable_intr_mask','get_intraw_mask','get_intsts_mask','clr_intsts_mask','get_intr_ena_status','read_rxfifo','write_txfifo','get_rxfifo_len','get_txfifo_len','txfifo_rst']+(['rxfifo_rst'] if args.all_ports else []) helpers=[] for name in names: match=re.search(r'^(?:FORCE_INLINE_ATTR|static inline) [^\n]*\buart_ll_'+name+r'\([^\n]*\)\n\{',ll,re.M);assert match,name @@ -53,13 +128,14 @@ def addr(name):return 0x3ff40000+int(re.search(r'#define UART_'+name+r'_REG\(i\) api=C.CDLL(str(lib));pointer=C.c_void_p def bind(name,result,*params):f=getattr(api,name);f.restype=result;f.argtypes=list(params);return f ai=bind('esk_alloc_init',None,C.POINTER(Allocator),pointer);create=bind('esk_esp32_machine_create',C.c_int,C.POINTER(Allocator),C.POINTER(pointer));destroy=bind('esk_esp32_machine_destroy',None,pointer) - attach=bind('esk_esp32_machine_uart_configure',C.c_int,pointer,C.c_int,C.c_int,C.c_int,C.c_int) - receive=bind('esk_esp32_machine_uart_receive',C.c_int,pointer,C.c_uint32,C.c_uint) + attach=bind('esk_esp32_machine_uart_port_configure',C.c_int,pointer,C.c_uint,C.c_int,C.c_int,C.c_int,C.c_int) + receive=bind('esk_esp32_machine_uart_port_receive',C.c_int,pointer,C.c_uint,C.c_uint32,C.c_uint) callback=C.CFUNCTYPE(None,pointer,C.POINTER(Frame),C.c_uint64);frames=[];times=[] def emitted(ctx,frame,offset32): frames.append(tuple(getattr(frame.contents,n) for n,_ in Frame._fields_));times.append(offset32) emit=callback(emitted) - advance=bind('esk_esp32_machine_uart_advance',C.c_int,pointer,C.c_uint64,callback,pointer) + advance=bind('esk_esp32_machine_uart_port_advance',C.c_int,pointer,C.c_uint,C.c_uint64,callback,pointer) + inputs=bind('esk_esp32_machine_uart_port_inputs',C.c_int,pointer,C.c_uint,C.c_int,C.c_int,C.c_int) bus_get=bind('esk_esp32_machine_bus',Memory,pointer,C.c_uint);fabric=bind('esk_esp32_machine_fabric',pointer,pointer) load=bind('esk_esp32_fabric_load',C.c_int,pointer,C.c_uint,C.c_uint32,pointer,C.c_size_t) configure=bind('esk_esp32_machine_configure',C.c_int,pointer,C.c_uint,C.c_uint32);core=bind('esk_esp32_machine_core',C.POINTER(Windows),pointer,C.c_uint) @@ -69,24 +145,26 @@ def emitted(ctx,frame,offset32): try: bus=bus_get(m,0) for trial in range(512): - assert attach(m,1,1,0,0)==0;frames.clear();times.clear() + port=trial%3 if args.all_ports else 0 + assert attach(m,port,1,1,0,0)==0;frames.clear();times.clear() bits=rng.randrange(5,9);parity=rng.randrange(2);odd=rng.randrange(2);stop=rng.randrange(1,3);data=rng.randrange(256);div=rng.randrange(2,130);frag=rng.randrange(16);apb=rng.randrange(2) conf=(apb<>n)&1 for n in range(bits)] p=(sum(bitstream)%2)^odd if parity:bitstream.append(p) halves=[b for b in bitstream for _ in range(2)]+[1]*(2 if stop==1 else 3) divider=div*16+frag;duration=divider*len(halves);tick=0 while tick*32>field('TXD'))&1==halves[tick*32//divider],(trial,tick,value.value,halves) - comparisons+=1;assert advance(m,1,emit,None)==0;tick+=1 + comparisons+=1;assert advance(m,port,1,emit,None)==0;tick+=1 assert frames==[(data&((1<\n#define DR_REG_UART_BASE 0x3ff40000\n#define BIT(n) (1U<<(n))\n',encoding='utf-8') source=ll[:ll.index('#pragma once')]+''' @@ -101,8 +179,8 @@ def emitted(ctx,frame,offset32): #define READ_PERI_REG(r) (*(volatile uint32_t *)(r)) #define WRITE_PERI_REG(r,v) (*(volatile uint32_t *)(r)=(v)) '''+ '\n'.join(helpers)+''' -unsigned work(unsigned seed) { - uart_dev_t *hw=&UART0; +unsigned work(unsigned seed,unsigned port) { + uart_dev_t *hw=port==0 ? &UART0 : port==1 ? &UART1 : &UART2; volatile unsigned *phase=(volatile unsigned *)0x3ffb9000; uint8_t tx[16],rx[16];unsigned sum=0; uart_ll_disable_intr_mask(hw,0x7ffff);uart_ll_clr_intsts_mask(hw,0x7ffff); @@ -121,6 +199,8 @@ def emitted(ctx,frame,offset32): return sum; } ''' + if not args.all_ports:source=source.replace('unsigned work(unsigned seed,unsigned port) {\n uart_dev_t *hw=port==0 ? &UART0 : port==1 ? &UART1 : &UART2;', 'unsigned work(unsigned seed) {\n uart_dev_t *hw=&UART0;') + if args.all_ports:source=source.replace(' return sum;', ' *phase=3;while(*phase!=4) { }\n uart_ll_rxfifo_rst(hw);\n return sum;') src=out/'work.c';obj=out/'work.o';elf=out/'work.elf';binary=out/'work.bin';src.write_text(source,encoding='utf-8') subprocess.run([args.compiler,'-B'+str(Path(args.compiler).parent)+'/','-O1','-ffreestanding','-fno-builtin','-mtext-section-literals','-c',str(src),'-o',str(obj)],check=True) subprocess.run([args.linker,'--no-relax','-Ttext=0x40080000','--entry=work','--defsym=UART0=0x3ff40000','--defsym=UART1=0x3ff50000','--defsym=UART2=0x3ff6e000',str(obj),'-o',str(elf)],check=True) @@ -129,12 +209,17 @@ def emitted(ctx,frame,offset32): code=binary.read_bytes();assert load(fabric(m),0,0x40080000,C.c_char_p(code),len(code))==0 assert bus.write(bus.ctx,0x3ff00030,4,1)==0 for cpu in range(2): - for trial in range(64): - assert attach(m,1,1,0,0)==0;frames.clear();times.clear() - assert bus.write(bus.ctx,addr('CONF1'),4,7|(96<<8))==0 + for trial in range(192 if args.all_ports else 64): + port=trial//64 if args.all_ports else 0 + assert attach(m,port,1,1,0,0)==0;frames.clear();times.clear() + if args.all_ports and port==1: + assert attach(m,2,1,1,1,0)==0 + assert bus.write(bus.ctx,addr('CONF0',2),4,0x0800801c)==0 + assert bus.write(bus.ctx,addr('FIFO',2),4,0xab)==0 + assert bus.write(bus.ctx,addr('CONF1',port),4,7|(96<<8))==0 assert bus.write(bus.ctx,0x3ffb9000,4,0)==0 assert configure(m,cpu,symbols['work'])==0;w=core(m,cpu).contents;w.ps|=1<<16 - seed=rng.randrange(256);w.cpu.a[1]=0x3ffd0000+cpu*4096;w.cpu.a[4]=0x7ffffff0;w.cpu.a[6]=seed + seed=rng.randrange(256);w.cpu.a[1]=0x3ffd0000+cpu*4096;w.cpu.a[4]=0x7ffffff0;w.cpu.a[6]=seed;w.cpu.a[7]=port assert run(m,cpu,1)==0;injected=False;rx=[rng.randrange(256) for _ in range(16)] for turn in range(6000): if w.cpu.pc==0x7ffffff0:break @@ -142,17 +227,25 @@ def emitted(ctx,frame,offset32): instructions+=1 assert bus.read(bus.ctx,0x3ffb9000,4,C.byref(value))==0 if value.value==1 and not injected: - assert advance(m,1000000,emit,None)==0 + assert advance(m,port,1000000,emit,None)==0 assert [f[0] for f in frames]==[(seed+i)&255 for i in range(16)] assert times==[(i+1)*20*16*0x2b6 for i in range(16)] - for data in rx:assert receive(m,data,0)==0 + for data in rx:assert receive(m,port,data,0)==0 assert bus.write(bus.ctx,0x3ffb9000,4,2)==0;injected=True + elif args.all_ports and value.value==3: + for data in [0xee,0xdd,0xcc]:assert receive(m,port,data,0)==0 + assert bus.write(bus.ctx,0x3ffb9000,4,4)==0 else:raise AssertionError('UART firmware budget exceeded') assert injected and w.cpu.a[6]==sum(rx),(cpu,trial,hex(w.cpu.a[6]),sum(rx)) + if args.all_ports: + assert bus.read(bus.ctx,addr('STATUS',port),4,C.byref(value))==0 and value.value&255==0 + if args.all_ports and port==1: + assert bus.read(bus.ctx,addr('STATUS',2),4,C.byref(value))==0 and (value.value>>16)&255==1 + assert attach(m,2,0,0,0,0)==0 calls+=1 finally:destroy(m) assert allocator.live_bytes==0 - print(f'{comparisons} independent UART half-bit/frame comparisons; {calls} actual ESP32 C interactive calls, {instructions} instructions, eleven unchanged SDK helpers; FIFO, IRQ and native APB/AHB paths') + print(f'{comparisons} independent UART half-bit/frame comparisons; {shared_turns} independent shared-RAM turns; {calls} actual ESP32 C interactive calls, {instructions} instructions, {len(names)} unchanged SDK helpers; FIFO, IRQ and native APB/AHB paths') if __name__=='__main__':main() From 70c5eccb2241ef5749b67148bc739c6677910427 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 22:22:46 +0000 Subject: [PATCH 38/57] Add per-core ESP32 SDK mask-ROM mapping and coherent data alias --- CHANGELOG.md | 13 ++ core/mcu/esp32/fabric.c | 33 ++++- core/mcu/esp32/fabric.h | 14 ++ docs/STATUS.md | 11 +- docs/engineering/esp32-boot-verification.md | 16 ++- docs/engineering/esp32-dual-core.md | 6 +- docs/engineering/esp32-mask-rom.md | 126 ++++++++++++++++++ tests/unit/test_esp32_mask_rom.c | 88 ++++++++++++ tests/vendor/README.md | 6 + .../boot-with-mask-rom-2026-11-05.json | 95 +++++++++++++ tests/vendor/xtensa_integer/probe_rom_boot.py | 5 + tests/vendor/xtensa_integer/run_mask_rom.py | 110 +++++++++++++++ 12 files changed, 512 insertions(+), 11 deletions(-) create mode 100644 docs/engineering/esp32-mask-rom.md create mode 100644 tests/unit/test_esp32_mask_rom.c create mode 100644 tests/vendor/xtensa_integer/evidence/boot-with-mask-rom-2026-11-05.json create mode 100644 tests/vendor/xtensa_integer/run_mask_rom.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 16f4d43e0..573c768fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Original ESP32 per-core SDK mask-ROM mapping + +- Added explicit 448 KiB PRO/APP ROM profiles with coherent IROM/DROM views, + protected accesses, independent ownership, low-ROM remap precedence and + atomic replacement/fallback. Existing TRM-only ROM fixtures remain unchanged. +- Resolved the SDK/QEMU mapping discrepancy explicitly: the TRM lists the last + 64 KiB instruction range reserved, while SDK ROM symbols and Espressif's + reference use it. No vendor image or missing byte is invented by the API. +- 1,170 native assertions, 18,840 independent mapping comparisons and 1,024 + compiled C calls to two unchanged ROM getters pass. The additional explicit + profile advances native ROM to 6,796 retirements and TIMG0 RTCCALICFG1 read; + physical contents, calibration and full SDK boot remain unverified. + ### Original ESP32 UART bank and shared FIFO RAM - Extended normal asynchronous execution to UART1/2, with per-port sources diff --git a/core/mcu/esp32/fabric.c b/core/mcu/esp32/fabric.c index 7e9370827..a8b3b55ab 100644 --- a/core/mcu/esp32/fabric.c +++ b/core/mcu/esp32/fabric.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 */ /* Copyright 2026 Junior Deogracias */ -/* Address facts: ESP32 TRM v5.9, section 3.3.2. No vendor code copied. */ +/* Static map: ESP32 TRM v5.9 3.3.2; optional mask-ROM profile: ESP-IDF + * v4.4.7 and Espressif QEMU 9.2.2 map/alias facts. No vendor code copied. */ #include "mcu/esp32/fabric.h" #include "mcu/esp32/flashmap.h" #include "mcu/esp32/efuse.h" @@ -9,7 +10,7 @@ struct view { struct esk_esp32_fabric *f;unsigned cpu; }; struct esk_esp32_fabric { esk_alloc *a; unsigned char sram0[0x20000],sram1[0x20000],sram2[0x32000],fast[0x2000],slow[0x2000]; - unsigned char *rom[2];esk_esp32_flashmap *flash; + unsigned char *rom[2],*mask_rom[2];esk_esp32_flashmap *flash; esk_esp32_efuse efuse; unsigned remap[2];struct view view[2]; }; @@ -26,14 +27,21 @@ static es_status locate(struct view *v,uint32_t addr,struct location *p) if(f->remap[v->cpu] && within(addr,0x400b0000,0x8000)) return ES_EUNSUPPORTED; offset=addr-0x400a0000;p->byte=f->sram1+(0x1fffcu-(offset&~3u))+(offset&3u); p->word_only=p->executable=1; - } else if(within(addr,0x40000000,0x60000)) { + } else if(within(addr,0x40000000,0x70000)) { if(f->remap[v->cpu] && addr<0x40008000) { offset=0x10000u+(addr-0x40000000);p->byte=f->sram1+(0x1fffcu-(offset&~3u))+(offset&3u); - } else { if(!f->rom[0]) return ES_EUNSUPPORTED;p->byte=f->rom[0]+(addr-0x40000000);p->readonly=1; } + } else { + if(f->mask_rom[v->cpu]) p->byte=f->mask_rom[v->cpu]+(addr-0x40000000); + else if(addr<0x40060000 && f->rom[0]) p->byte=f->rom[0]+(addr-0x40000000); + else return ES_EUNSUPPORTED; + p->readonly=1; + } p->word_only=p->executable=1; } else if(within(addr,0x3ff90000,0x10000)) { - if(!f->rom[1]) return ES_EUNSUPPORTED; - p->byte=f->rom[1]+(addr-0x3ff90000);p->readonly=1; + if(f->mask_rom[v->cpu]) p->byte=f->mask_rom[v->cpu]+0x60000u+(addr-0x3ff90000); + else if(f->rom[1]) p->byte=f->rom[1]+(addr-0x3ff90000); + else return ES_EUNSUPPORTED; + p->readonly=1; } else if(within(addr,0x3ff80000,0x2000)||within(addr,0x400c0000,0x2000)) { if(v->cpu) return ES_EUNSUPPORTED; offset=addr<0x40000000 ? addr-0x3ff80000 : addr-0x400c0000; @@ -108,6 +116,8 @@ void esk_esp32_fabric_destroy(esk_esp32_fabric *f) if(!f) return; if(f->rom[0]) esk_free(f->a,f->rom[0],0x60000); if(f->rom[1]) esk_free(f->a,f->rom[1],0x10000); + if(f->mask_rom[0]) esk_free(f->a,f->mask_rom[0],0x70000); + if(f->mask_rom[1]) esk_free(f->a,f->mask_rom[1],0x70000); esk_esp32_flashmap_destroy(f->flash); esk_free(f->a,f,sizeof *f); } @@ -118,6 +128,17 @@ es_status esk_esp32_fabric_rom(esk_esp32_fabric *f,unsigned which,const unsigned copy=(unsigned char *)esk_malloc(f->a,len);if(!copy) return ES_ENOMEM; memcpy(copy,data,len);if(f->rom[which]) esk_free(f->a,f->rom[which],len);f->rom[which]=copy;return ES_OK; } +es_status esk_esp32_fabric_mask_rom(esk_esp32_fabric *f,unsigned cpu,const unsigned char *data,size_t len) +{ + unsigned char *copy=NULL; + if(!f || cpu>1 || (!data && len) || (data && len!=0x70000u)) return ES_EINVAL; + if(data) { + copy=(unsigned char *)esk_malloc(f->a,len);if(!copy) return ES_ENOMEM; + memcpy(copy,data,len); + } + if(f->mask_rom[cpu]) esk_free(f->a,f->mask_rom[cpu],0x70000); + f->mask_rom[cpu]=copy;return ES_OK; +} es_status esk_esp32_fabric_flash(esk_esp32_fabric *f,const unsigned char *data,size_t len) { esk_esp32_flashmap *created=NULL;es_status st; diff --git a/core/mcu/esp32/fabric.h b/core/mcu/esp32/fabric.h index 3d74f6172..be23280f0 100644 --- a/core/mcu/esp32/fabric.h +++ b/core/mcu/esp32/fabric.h @@ -16,6 +16,20 @@ es_status esk_esp32_fabric_create(esk_alloc *a,esk_esp32_fabric **out); void esk_esp32_fabric_destroy(esk_esp32_fabric *f); /* Optional externally provided ROM0/ROM1, exact 384K/64K. No ROM is fabricated. */ es_status esk_esp32_fabric_rom(esk_esp32_fabric *f,unsigned which,const unsigned char *data,size_t len); +/* Explicit SDK mask-ROM profile: exact externally supplied 448K image per CPU. + * IROM 0x40000000..0x4006ffff; DROM 0x3ff90000..0x3ff9ffff aliases its + * final 64K. Both views read-only, IROM permits fetch/aligned data words, + * DROM permits narrow data but no fetch. SDK v4.4.7 defines this IROM range; + * Espressif QEMU uses this alias/image layout. TRM v5.9 marks the extension + * reserved: this opt-in profile does not alter the default 384K TRM view. + * PRO/APP inputs are independent; no image or missing bytes are fabricated. + * Low-32K boot-remap still takes precedence. NULL/0 detaches to legacy ROMs. + * Replacement failure preserves old bytes/aliases; RAM/remap/peer survive. + * No ROM-content/revision/authenticity, cache/bus/physical timing or APP boot + * qualification is inferred. Host configuration with CPUs paused, before CPU + * initialization or after explicit instruction-buffer invalidation. This host + * fixture API does not reset/invalidate CPU state or emulate silicon ROM writes. */ +es_status esk_esp32_fabric_mask_rom(esk_esp32_fabric *f,unsigned cpu,const unsigned char *data,size_t len); /* Opt into copied read-only external flash: whole 64K pages, at most 16MB. * Adds PID0/1 normal-mode MMU tables and a bounded cache-control/flush profile. * Tables start invalid by fixture policy. Edit while target cache disabled, diff --git a/docs/STATUS.md b/docs/STATUS.md index 005b57b98..df187f98d 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -7,7 +7,16 @@ does not mean the described behaviour exists. ## Original ESP32 dual-core target investigation (2026-11-04) -**Latest UART-bank update:** the [normal UART0/1/2 bank](engineering/esp32-uart-bank.md) +**Latest ROM-map update:** an [explicit per-core SDK mask-ROM profile](engineering/esp32-mask-rom.md) +now supplies the 448 KiB IROM view and coherent DROM tail alias using external +PRO/APP images. 1,170 native assertions, 18,840 independent comparisons and +1,024 compiled C calls to two unchanged ROM getters pass. The PRO diagnostic +with the additional profile reaches 6,796 retirements, then TIMG0 RTCCALICFG1 +read at 0x3ff5f06c/PC 0x40008591. The default TRM-only view and prior failure +remain available. Physical ROM contents, timer calibration, full SDK boot and +calibrated instruction timing remain unverified. + +**Prior UART-bank update:** the [normal UART0/1/2 bank](engineering/esp32-uart-bank.md) now shares one 1024-byte FIFO RAM, supports bounded allocations/overwrites and extended counts/thresholds, and coordinates idle UART1→UART2 TX reset. 271,883 new native assertions, 4,096 shared-RAM referee turns and 384 compiled C calls diff --git a/docs/engineering/esp32-boot-verification.md b/docs/engineering/esp32-boot-verification.md index ec2fb99dc..619351b6d 100644 --- a/docs/engineering/esp32-boot-verification.md +++ b/docs/engineering/esp32-boot-verification.md @@ -3,8 +3,8 @@ **Result on 2026-11-05: the owner's board reports ESP32-D0WD-V3 revision 3.1, JEDEC C4 60 16 and 4 MB flash. Giantec's GT25Q32A-U datasheet matches that ID; exact package/ordering suffix remains unconfirmed. The pinned SDK example boots -in Espressif QEMU. Native EmbSIM reaches an instruction-ROM mapping boundary after 6,790 retired -instructions with explicit synthetic eFuse/RTC, active GPIO and UART-bank fixtures; without an eFuse profile, +in Espressif QEMU. Native EmbSIM reaches timer calibration after 6,796 retired +instructions with explicit synthetic eFuse/RTC, active GPIO/UART-bank and mask-ROM fixtures; without an eFuse profile, the baseline stops at an eFuse read after seven.** Neither result establishes successful native boot. @@ -114,7 +114,17 @@ configuration or printed SDK capacity must not substitute for hardware identity. ## Native EmbSIM result: blocked -**Latest UART-bank follow-up:** [UART0/1/2 and shared RAM](esp32-uart-bank.md) +**Latest mask-ROM follow-up:** the [explicit per-core SDK mask-ROM profile](esp32-mask-rom.md) +resolves the extended IROM/DROM layout without changing the TRM-only default. +With `--mask-rom-profile`, PRO retires 6,796 instructions / 6,797 attempts, +then fails TIMG0 RTCCALICFG1 read at 0x3ff5f06c, PC 0x40008591, opcode 0x2222. +The API requires an explicit full image; the diagnostic pads only the declared +external reference tail like QEMU, not inferred silicon contents. The +[mask-ROM evidence](../../tests/vendor/xtensa_integer/evidence/boot-with-mask-rom-2026-11-05.json) +retains boot unverified. APP ROM getters are separately tested; APP reset/SDK +boot remains unqualified. + +**Prior UART-bank follow-up:** [UART0/1/2 and shared RAM](esp32-uart-bank.md) passes the previous UART1 interrupt clear. With `--uart-bank-active-fixture`, PRO retires 6,790 instructions / 6,791 attempts, then fails fetch at 0x40065c18 from a real CALL8 at 0x40007729. The current fabric exposes only the first 384 KiB diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index a98ca6cdd..3ffbaa514 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -47,7 +47,11 @@ physical pin/clock/USB behavior remain absent. [UART bank/shared RAM](esp32-uart-bank.md) now extends normal mode to UART1/2, bounded overlapping allocations and idle coupled reset. The all-port fixture reaches 6,790 retirements, then instruction-ROM fetch at 0x40065c18 outside the -currently exposed fabric window. Extended ROM mapping remains a separate task. +currently exposed fabric window in that fixture. +[Per-core SDK mask-ROM mapping](esp32-mask-rom.md) now supplies the extended +IROM/DROM alias explicitly, preserving the default TRM view. Its additional +fixture reaches 6,796 retirements, then TIMG0 RTCCALICFG1 read at 0x3ff5f06c. +No timer-calibration result or full SDK boot is claimed. [Instruction timing](esp32-instruction-timing.md) still requires physical evidence; a buildable capture firmware is prepared without claiming calibrated latency. It is not the owner's fuse dump and does not establish full boot. The [Giantec storage subset](esp32-giantec-profile.md) diff --git a/docs/engineering/esp32-mask-rom.md b/docs/engineering/esp32-mask-rom.md new file mode 100644 index 000000000..55566f117 --- /dev/null +++ b/docs/engineering/esp32-mask-rom.md @@ -0,0 +1,126 @@ +# Original ESP32 per-core SDK mask-ROM profile + +**Implemented: explicit per-core 448 KiB mask-ROM images, instruction access +through 0x4006ffff and a coherent final-64-KiB DROM alias. The default TRM-only +384 KiB instruction view remains available. This is not physical ROM-content +verification, APP reset/SDK boot, complete SoC mapping or calibrated timing.** + +## Resolving the mapping discrepancy + +The ESP32 TRM v5.9 table 3.3-2 calls 0x40060000..0x4006ffff reserved and lists +384 KiB ROM0 plus 64 KiB ROM1. The previous fabric implemented that static table. +However, ESP-IDF v4.4.7 `soc.h` defines `SOC_IROM_MASK_HIGH` as 0x40070000, and +its ROM linker script provides actual functions in the extension, including +`tdefl_get_prev_return_status` at 0x400608d0 and `tdefl_get_adler32` at 0x400608d8. + +Espressif QEMU's `hw/xtensa/esp32.c` independently confirms the SDK image layout: +448 KiB IROM at 0x40000000, with DROM at 0x3ff90000 aliasing its last 64 KiB. +It has distinct CPU-specific ROM regions and loads separate PRO/APP images. +The native ROM's actual CALL8 at 0x40007729 enters 0x40065c18, previously outside +the fabric window. Instruction bytes are present there in the external image. + +This change adds a named, explicit SDK/QEMU mapping profile. It does not silently +reinterpret the TRM-only fixture or claim the TRM reserved range is universally +usable on every silicon revision. Neither a simulator reference image nor the +owner's chip/flash ID authenticates their physical mask-ROM contents. + +## Observable behavior and internal model + +`esk_esp32_fabric_mask_rom(f, cpu, data, length)` requires exactly 0x70000 bytes +for the selected CPU. The API copies those supplied bytes; it fabricates no ROM, +missing tail or peer image. PRO and APP may use different inputs. Each image has +two coherent, read-only views: + +| View | Address range, inclusive | Image offset | Supported access | +| --- | --- | --- | --- | +| IROM | 0x40000000..0x4006ffff | 0..0x6ffff | Fetch and aligned data words | +| DROM | 0x3ff90000..0x3ff9ffff | 0x60000..0x6ffff | Data bytes/halfwords/words; no fetch | + +Low-32-KiB boot remap still takes precedence and uses the existing reversed-word +SRAM view. It does not redirect the extension or DROM. ROM stores and RAM-load +attempts fail without modifying either view; CAS/atomic memory classification +remain SRAM-only. Narrow instruction-window data operations, unaligned data +words, unsupported fetches and accesses crossing the end of the window fail +without changing the caller's output. Adjacent cache RAM remains unsupported. + +`NULL, 0` detaches one core's profile, exposing its legacy ROM0/ROM1 backing +again. The other core, SRAM, RTC memory and remap controls survive. Replacement +allocates/copies first, so allocation failure preserves the old image, both +aliases and allocator accounting. Legacy ROM installation stays independent; +it does not overwrite an attached profile. + +Each CPU uses one immutable backing allocation for both views. This models +aliasing directly instead of keeping two independently mutable copies. It is an +original C99 implementation of the mapping facts; no QEMU implementation code, +GPL runtime or vendor ROM bytes are copied into this repository. + +This is a host setup/debug API. Configure with CPUs paused, before initializing +CPU profiles or after explicitly invalidating their instruction buffers. The +fabric does not reset/invalidate CPU state, perform silicon ROM writes or invent +cache-coherence behavior when replacing a test image. Calls are serialized and +must not re-enter. Full cache/MPU/power/reset-domain behavior, address-decoder +latency, instruction timing and physical ROM authenticity remain unqualified. + +## Proof and reproduction + +- **1,170 native assertions** check per-core selection, IROM/DROM content, + alias edges, mixed-width fetch across the old boundary, copy isolation, RO/CAS + rejection, output preservation, low-ROM remap, peer retention, legacy fallback, + invalid inputs, allocation failure and leak-free teardown. +- **18,840 independent per-core fetch/data/alias comparisons** use separate + pinned external PRO/APP images, random offsets and their differing bytes. + The reference parses the SDK range and checks the pinned QEMU mapping/alias. +- **1,024 actual ESP32 C calls / 16,896 retired instructions** execute two + unchanged external ROM getter bodies on both native cores. The compiler uses + the unmodified SDK miniz header to write the real structure fields, then calls + the ROM symbols at their linked addresses. All four defined status values and + full-width Adler fields are exercised. Host code does not implement a getter + callback or skip its PC. This qualifies those getters and mappings, not the + compression engine, all ROM routines or APP reset/SDK boot. + +```sh +python3 tests/vendor/xtensa_integer/run_mask_rom.py \ + --pro-rom /path/to/esp32-v3-rom.bin \ + --app-rom /path/to/esp32-v3-rom-app.bin \ + --soc-header "$IDF_PATH/components/soc/esp32/include/soc/soc.h" \ + --rom-symbols "$IDF_PATH/components/esp_rom/esp32/ld/esp32.rom.ld" \ + --miniz-header "$IDF_PATH/components/esp_rom/include/esp32/rom/miniz.h" \ + --qemu-source /path/to/esp32.c \ + --compiler xtensa-esp32-elf-gcc --objcopy xtensa-esp32-elf-objcopy \ + --linker xtensa-esp32-elf-ld --nm xtensa-esp32-elf-nm +``` + +External SDK inputs are from +[ESP-IDF v4.4.7, commit 38eeba213aa695aabfd6d89aa9f5078dbe5a94c3](https://github.com/espressif/esp-idf/tree/38eeba213aa695aabfd6d89aa9f5078dbe5a94c3). +Reference mapping source is +[Espressif QEMU commit 40edccac415693c5130f91c01d84176ae6008566](https://github.com/espressif/qemu/blob/40edccac415693c5130f91c01d84176ae6008566/hw/xtensa/esp32.c), +the `esp-develop-9.2.2-20260417` release. Headers/images/source are external test +inputs, never bundled simulator runtime code. The SDK header is used unchanged +in a temporary compilation directory. + +| External input | SHA-256 | +| --- | --- | +| PRO ROM binary | `9115bbcd03da9f54d3baca5ad94d588a43da54ed332347137ba3dc9010df258b` | +| APP ROM binary | `6d3240e7cdb1ec9c1d683515e0a8578e9fb8ece3e7736cb9a996512c656e8442` | +| SDK `soc.h` | `7a7148c288d7c289c961810e5617fa7b14d105b28349a45f772150b1e0845dd3` | +| SDK `esp32.rom.ld` | `0160cb9e14e83de87ad7363bd4977a9dd353b22e868a9bd4474e167ee86e58ec` | +| SDK `rom/miniz.h` | `3d43ce51f596fbb8226a55c0c7f63693606c953a45f3c92ac7f33d4147ccf74d` | +| QEMU `hw/xtensa/esp32.c` | `dcbdb0c67e2398e73e0d17b2f51d0f300161829b7bffd1c34b3114b168846d3f` | + +## Native ROM boot boundary + +Add **`--mask-rom-profile`** to the existing native diagnostic with explicit +synthetic eFuse/RTC/GPIO/UART-bank fixtures. The external release image has +455,722 bytes. The diagnostic explicitly pads its absent tail to 448 KiB with +zeros to match QEMU's empty-region fixture policy; this is not measured silicon +content. The API itself requires every supplied byte and performs no padding. +APP is not enabled by this PRO-only diagnostic. + +The ROM now retires **6,796 instructions / 6,797 attempts**, executing the +formerly blocked entry at 0x40065c18. It next fails a **TIMG0 RTCCALICFG1 read at +0x3ff5f06c**, PC **0x40008591**, opcode **0x2222**. The result is still blocked: +no calibration result, clock frequency or ready event is fabricated to pass it. +[Hash-recorded evidence](../../tests/vendor/xtensa_integer/evidence/boot-with-mask-rom-2026-11-05.json) +retains `boot_verified: false`. Without the new flag, the old 6,790-instruction +mapping failure remains reproducible. Full native ROM/SDK boot and calibrated +instruction timing remain open. diff --git a/tests/unit/test_esp32_mask_rom.c b/tests/unit/test_esp32_mask_rom.c new file mode 100644 index 000000000..6d764c62c --- /dev/null +++ b/tests/unit/test_esp32_mask_rom.c @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/fabric.h" +static uint32_t word(const unsigned char *image,unsigned offset) +{ return (uint32_t)image[offset]|((uint32_t)image[offset+1u]<<8)|((uint32_t)image[offset+2u]<<16)|((uint32_t)image[offset+3u]<<24); } +static void aliases_boundaries_and_remap(void) +{ + esk_alloc a;esk_esp32_fabric *f=NULL;esk_xtensa_memory b[2];unsigned cpu,i,j;uint32_t v,old; + unsigned char *image=(unsigned char *)malloc(0x70000);const unsigned char code[]={0x36,0x41,0,0}; + const uint32_t offsets[]={0,0x7ffcu,0x8000u,0x5fffcu,0x60000u,0x65c18u,0x6fffcu}; + CHECK(image!=NULL);if(!image) return; + esk_alloc_init(&a,NULL);CHECK_EQ(esk_esp32_fabric_create(&a,&f),ES_OK); + for(cpu=0;cpu<2;cpu++) b[cpu]=esk_esp32_fabric_bus(f,cpu); + memset(image,0x5a,0x70000);CHECK_EQ(esk_esp32_fabric_rom(f,0,image,0x60000),ES_OK); + CHECK_EQ(esk_esp32_fabric_rom(f,1,image,0x10000),ES_OK); + for(cpu=0;cpu<2;cpu++) { + v=0xdeadbeef;CHECK_EQ(b[cpu].fetch(b[cpu].ctx,0x40060000,3,&v),ES_EUNSUPPORTED);CHECK_EQ(v,0xdeadbeefu); + for(i=0;i<0x70000u;i++) image[i]=(unsigned char)((i*13u+(i>>12)+cpu*71u)&255u); + CHECK_EQ(esk_esp32_fabric_mask_rom(f,cpu,image,0x70000),ES_OK); + for(j=0;jm_adler32=value^0xa531d497U; + d->m_prev_return_status=(tdefl_status)((int)(value&3U)-2); + return which ? tdefl_get_adler32(d) : (unsigned)tdefl_get_prev_return_status(d); +} +''',encoding='utf-8') + subprocess.run([args.compiler,'-B'+str(Path(args.compiler).parent)+'/','-O1','-ffreestanding','-fno-builtin','-mtext-section-literals','-c',str(src),'-o',str(obj)],check=True) + subprocess.run([args.linker,'--no-relax','-Ttext=0x40080000','--entry=work',*[f'--defsym={name}={entry:#x}' for name,entry in entries.items()],str(obj),'-o',str(elf)],check=True) + subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(elf),str(binary)],check=True) + labels={f[2]:int(f[0],16) for line in subprocess.check_output([args.nm,str(elf)],text=True).splitlines() if len(f:=line.split())==3} + code=binary.read_bytes();assert load(ram,0,0x40080000,C.c_char_p(code),len(code))==0 + bus=bus_get(m,0);assert bus.write(bus.ctx,0x3ff00030,4,1)==0 + for cpu in range(2): + for trial in range(256): + data=rng.getrandbits(32) + for which in range(2): + assert configure(m,cpu,labels['work'])==0;w=core(m,cpu).contents;w.ps|=1<<16 + w.cpu.a[1]=0x3ffd0000+cpu*4096;w.cpu.a[4]=0x7ffffff0;w.cpu.a[6]=data;w.cpu.a[7]=which + assert run(m,cpu,1)==0;seen=False + for turn in range(200): + if w.cpu.pc==0x7ffffff0:break + seen|=w.cpu.pc in entries.values() + assert step(m,cpu,C.byref(event),C.byref(fault))==0 and event.value==0,(hex(fault.pc),hex(fault.opcode),hex(fault.address),event.value) + instructions+=1 + else:raise AssertionError('ROM helper firmware budget exceeded') + assert seen and w.cpu.a[6]==((data^0xa531d497) if which else (((data&3)-2)&0xffffffff)) + calls+=1 + finally:destroy(m) + assert allocator.live_bytes==0 + print(f'{comparisons} independent per-core ROM fetch/data/alias comparisons; {calls} actual ESP32 C calls, {instructions} retired instructions; two unchanged external ROM helpers, distinct PRO/APP images, SDK structure layout') + + +if __name__=='__main__':main() From 42c558d7674759477a0a3d7bd4351bd50fd74221 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Thu, 5 Nov 2026 23:22:05 +0000 Subject: [PATCH 39/57] Model ESP32 TIMG0 RTC calibration with external clock periods --- CHANGELOG.md | 14 ++ core/mcu/esp32/machine.c | 11 +- core/mcu/esp32/machine.h | 5 + core/mcu/esp32/rtc_cal.c | 68 ++++++++ core/mcu/esp32/rtc_cal.h | 31 ++++ docs/STATUS.md | 11 +- docs/engineering/esp32-boot-verification.md | 17 +- docs/engineering/esp32-dual-core.md | 6 +- docs/engineering/esp32-rtc-calibration.md | 158 ++++++++++++++++++ tests/unit/test_esp32_rtc_cal.c | 107 ++++++++++++ tests/vendor/README.md | 7 + .../boot-with-rtc-cal-period-2026-11-06.json | 100 +++++++++++ ...oot-with-rtc-cal-unclocked-2026-11-06.json | 99 +++++++++++ tests/vendor/xtensa_integer/probe_rom_boot.py | 22 ++- tests/vendor/xtensa_integer/run_atomctl.py | 2 +- tests/vendor/xtensa_integer/run_controls.py | 2 +- tests/vendor/xtensa_integer/run_efuse.py | 2 +- tests/vendor/xtensa_integer/run_flash_c.py | 2 +- tests/vendor/xtensa_integer/run_gpio.py | 2 +- tests/vendor/xtensa_integer/run_ipi_c.py | 2 +- tests/vendor/xtensa_integer/run_machine_c.py | 2 +- tests/vendor/xtensa_integer/run_mask_rom.py | 2 +- tests/vendor/xtensa_integer/run_memctl.py | 2 +- tests/vendor/xtensa_integer/run_regions.py | 2 +- tests/vendor/xtensa_integer/run_reset_c.py | 2 +- tests/vendor/xtensa_integer/run_rtc.py | 2 +- tests/vendor/xtensa_integer/run_rtc_cal.py | 148 ++++++++++++++++ tests/vendor/xtensa_integer/run_spi_c.py | 2 +- tests/vendor/xtensa_integer/run_tls_c.py | 2 +- tests/vendor/xtensa_integer/run_uart.py | 2 +- 30 files changed, 810 insertions(+), 24 deletions(-) create mode 100644 core/mcu/esp32/rtc_cal.c create mode 100644 core/mcu/esp32/rtc_cal.h create mode 100644 docs/engineering/esp32-rtc-calibration.md create mode 100644 tests/unit/test_esp32_rtc_cal.c create mode 100644 tests/vendor/xtensa_integer/evidence/boot-with-rtc-cal-period-2026-11-06.json create mode 100644 tests/vendor/xtensa_integer/evidence/boot-with-rtc-cal-unclocked-2026-11-06.json create mode 100644 tests/vendor/xtensa_integer/run_rtc_cal.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 573c768fe..2c889594a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Original ESP32 TIMG0 RTC calibration counter + +- Added explicit calibration snapshots, selected-period XTAL-tick counting, + one-shot READY/completed results, single-period cycling, stalls and rearm. + CPU execution/MMIO reads do not manufacture clock progress. +- Added RO protection, unsupported-mode/overflow rollback, shared CPU buses and + CPU-reset retention. Full clock-tree/gating/oscillator behavior, larger cycling + windows, TIMG1 and physical timing remain unqualified. +- 919,967 native assertions, 19,208 independent comparisons and 486 compiled + C harness calls using ten unchanged SDK access macros pass. This does not + claim the full SDK calibration function runs unchanged. +- Native ROM reaches 6,872 retirements, then per-pin pad-hold read, with explicit + unclocked/synthetic-period fixtures. Both preserve boot-unverified status. + ### Original ESP32 per-core SDK mask-ROM mapping - Added explicit 448 KiB PRO/APP ROM profiles with coherent IROM/DROM views, diff --git a/core/mcu/esp32/machine.c b/core/mcu/esp32/machine.c index 02b7c7457..c69ae0eb7 100644 --- a/core/mcu/esp32/machine.c +++ b/core/mcu/esp32/machine.c @@ -9,7 +9,7 @@ struct esk_esp32_machine { uint32_t app_clock,app_stall,app_boot,app_reset,app_reset_enabled,app_reset_vector,app_reset_pending; unsigned spi1_owned,efuse_owned,uart_owned[3]; uint32_t rtc_enabled,rtc_state,rtc_store[8]; - esk_esp32_gpio gpio;esk_esp32_uart_bank uart; + esk_esp32_rtc_cal calibration;esk_esp32_gpio gpio;esk_esp32_uart_bank uart; esk_esp32_matrix matrix;struct machine_view view[2]; }; /* Never mutate an executing CPU from a bus callback. Synchronize the @@ -108,6 +108,7 @@ static es_status control_write(esk_esp32_machine *m,uint32_t address,unsigned by static es_status bus_read(void *ctx,uint32_t address,unsigned bytes,uint32_t *value) { struct machine_view *v=(struct machine_view *)ctx;esk_xtensa_memory ram;es_status st; + st=esk_esp32_rtc_cal_read(&v->m->calibration,address,bytes,value);if(st!=ES_EUNSUPPORTED) return st; st=control_read(v->m,address,bytes,value);if(st!=ES_EUNSUPPORTED) return st; st=esk_esp32_uart_bank_read(&v->m->uart,address,bytes,value); if(st!=ES_EUNSUPPORTED) { if(st==ES_OK && !v->m->executing) sync_inputs(v->m);return st; } @@ -119,6 +120,7 @@ static es_status bus_read(void *ctx,uint32_t address,unsigned bytes,uint32_t *va static es_status bus_write(void *ctx,uint32_t address,unsigned bytes,uint32_t value) { struct machine_view *v=(struct machine_view *)ctx;esk_xtensa_memory ram;es_status st; + st=esk_esp32_rtc_cal_write(&v->m->calibration,address,bytes,value);if(st!=ES_EUNSUPPORTED) return st; st=control_write(v->m,address,bytes,value); if(st!=ES_EUNSUPPORTED) { if(st==ES_OK && !v->m->executing) { apply_reset(v->m);sync_inputs(v->m); } @@ -212,7 +214,7 @@ es_status esk_esp32_machine_create(esk_alloc *a,esk_esp32_machine **out) m=(esk_esp32_machine *)esk_calloc(a,1,sizeof *m);if(!m) return ES_ENOMEM; m->a=a;st=esk_esp32_fabric_create(a,&m->fabric); if(st!=ES_OK) { esk_free(a,m,sizeof *m);return st; } - esk_esp32_matrix_init(&m->matrix);esk_esp32_gpio_init(&m->gpio);esk_esp32_uart_bank_init(&m->uart); + esk_esp32_matrix_init(&m->matrix);esk_esp32_gpio_init(&m->gpio);esk_esp32_uart_bank_init(&m->uart);esk_esp32_rtc_cal_init(&m->calibration); for(i=0;i<2;i++) { esk_xtensa_windows_init(&m->cpu[i],0,NULL);m->view[i].m=m;m->view[i].cpu=i; } *out=m;return ES_OK; } @@ -311,3 +313,8 @@ es_status esk_esp32_machine_uart_timeout(esk_esp32_machine *m) { return esk_esp32_machine_uart_port_timeout(m,0); } es_status esk_esp32_machine_uart_advance(esk_esp32_machine *m,uint64_t ticks,esk_esp32_uart_emit emit,void *ctx) { return esk_esp32_machine_uart_port_advance(m,0,ticks,emit,ctx); } + +es_status esk_esp32_machine_rtc_cal_configure(esk_esp32_machine *m,int on,uint32_t control,uint32_t value) +{ return !m || m->executing ? ES_EINVAL : esk_esp32_rtc_cal_configure(&m->calibration,on,control,value); } +es_status esk_esp32_machine_rtc_cal_period(esk_esp32_machine *m,unsigned source,uint32_t xtal_ticks) +{ return !m || m->executing ? ES_EINVAL : esk_esp32_rtc_cal_period(&m->calibration,source,xtal_ticks); } diff --git a/core/mcu/esp32/machine.h b/core/mcu/esp32/machine.h index bfb85f564..2eab85645 100644 --- a/core/mcu/esp32/machine.h +++ b/core/mcu/esp32/machine.h @@ -6,6 +6,7 @@ #include "mcu/esp32/matrix.h" #include "mcu/esp32/gpio.h" #include "mcu/esp32/uart.h" +#include "mcu/esp32/rtc_cal.h" #include "mcu/xtensa/windows.h" typedef struct esk_esp32_machine esk_esp32_machine; /* Experimental two-context execution harness over shared internal memory. @@ -58,6 +59,10 @@ es_status esk_esp32_machine_uart_advance(esk_esp32_machine *m,uint64_t ticks,esk es_status esk_esp32_machine_source(esk_esp32_machine *m,unsigned source,int asserted); es_status esk_esp32_machine_gpio_irq(esk_esp32_machine *m,unsigned cpu,int nmi,int asserted); es_status esk_esp32_machine_nmi_mask(esk_esp32_machine *m,unsigned cpu,int asserted); +/* Explicit TIMG0 calibration profile and full selected-clock periods; see + * rtc_cal.h. No frequencies, elapsed CPU time or oscillator history inferred. */ +es_status esk_esp32_machine_rtc_cal_configure(esk_esp32_machine *m,int on,uint32_t control,uint32_t value); +es_status esk_esp32_machine_rtc_cal_period(esk_esp32_machine *m,unsigned source,uint32_t xtal_ticks); /* Opt-in RTC reset-state and eight retention-register snapshot. All eight * initial words are explicit because hardware power-on contents are undefined. * reset_state supplies only bits 13:0: vector selects and raw cause latches; diff --git a/core/mcu/esp32/rtc_cal.c b/core/mcu/esp32/rtc_cal.c new file mode 100644 index 000000000..3d6edea89 --- /dev/null +++ b/core/mcu/esp32/rtc_cal.c @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* ESP32 TRM v5.9 TIMG registers and ESP-IDF v4.4.7 rtc_time.c protocol. + * Original implementation. External complete-period inputs, no guessed clocks. */ +#include "mcu/esp32/rtc_cal.h" +#include +#define CONTROL UINT32_C(0x3ff5f068) +#define RESULT UINT32_C(0x3ff5f06c) +#define RW UINT32_C(0xffff7000) +#define START UINT32_C(0x80000000) +#define CYCLE UINT32_C(0x1000) +#define LIMIT UINT32_C(0x1ffffff) +static int valid_control(uint32_t value) +{ + uint32_t mode=value&(START|CYCLE); + return ((value>>13)&3u)!=3u && mode!=(START|CYCLE) && + (!mode || ((value>>16)&0x7fffu)) && (!(value&CYCLE) || ((value>>16)&0x7fffu)==1u); +} +void esk_esp32_rtc_cal_init(esk_esp32_rtc_cal *c) { if(c) memset(c,0,sizeof *c); } +es_status esk_esp32_rtc_cal_configure(esk_esp32_rtc_cal *c,int on,uint32_t control,uint32_t value) +{ + if(!c || (on!=0 && on!=1)) return ES_EINVAL; + if(on && ((control&~RW) || value>LIMIT || !valid_control(control))) return ES_EUNSUPPORTED; + esk_esp32_rtc_cal_init(c);if(!on) return ES_OK; + c->enabled=1;c->control=control;c->value=value;c->active=(control&(START|CYCLE)) ? 1u : 0u;return ES_OK; +} +static es_status access_ok(const esk_esp32_rtc_cal *c,uint32_t address,unsigned bytes) +{ + if(!c) return ES_EINVAL; + if(!c->enabled || address=RESULT+4u) return ES_EUNSUPPORTED; + return bytes==4 && !(address&3u) ? ES_OK : ES_EINVAL; +} +es_status esk_esp32_rtc_cal_read(const esk_esp32_rtc_cal *c,uint32_t address,unsigned bytes,uint32_t *value) +{ + es_status st;if(!value) return ES_EINVAL; + st=access_ok(c,address,bytes);if(st!=ES_OK) return st; + *value=address==CONTROL ? c->control|(c->ready<<15) : c->value<<7;return ES_OK; +} +es_status esk_esp32_rtc_cal_write(esk_esp32_rtc_cal *c,uint32_t address,unsigned bytes,uint32_t value) +{ + es_status st=access_ok(c,address,bytes);uint32_t next=value&RW,previous; + if(st!=ES_OK) return st; + if(address==RESULT) return ES_OK; /* Known read-only result. */ + if(!valid_control(next)) return ES_EUNSUPPORTED; + if(c->active && (c->control&START) && (next&START) && ((next^c->control)&UINT32_C(0x7fff6000))) return ES_EUNSUPPORTED; + previous=c->control;if(previous==next) return ES_OK; + c->control=next; + if(!(next&(START|CYCLE))) { c->active=0;c->periods=c->ticks=0; } + else if((next&CYCLE) || !(previous&START)) { + c->active=1;c->ready=0;c->periods=c->ticks=0; + } + return ES_OK; +} +es_status esk_esp32_rtc_cal_period(esk_esp32_rtc_cal *c,unsigned source,uint32_t xtal_ticks) +{ + uint64_t sum; + if(!c || source>2) return ES_EINVAL; + if(!c->enabled) return ES_EUNSUPPORTED; + if(!c->active || source!=((c->control>>13)&3u)) return ES_OK; + if(!xtal_ticks) return ES_EUNSUPPORTED; + sum=(uint64_t)c->ticks+xtal_ticks;if(sum>LIMIT) return ES_EUNSUPPORTED; + c->ticks=(uint32_t)sum;c->periods++; + if(c->periods==((c->control>>16)&0x7fffu)) { + c->value=c->ticks;c->periods=c->ticks=0; + if(c->control&START) { c->ready=1;c->active=0; } + } + return ES_OK; +} diff --git a/core/mcu/esp32/rtc_cal.h b/core/mcu/esp32/rtc_cal.h new file mode 100644 index 000000000..09ced3aae --- /dev/null +++ b/core/mcu/esp32/rtc_cal.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#ifndef ESK_ESP32_RTC_CAL_H +#define ESK_ESP32_RTC_CAL_H +#include "embsim/types.h" +/* TIMG0 RTC-calibration counter profile, not the timer/watchdog/clock tree. + * Explicit initial control/value; no oscillator frequency or past measurement + * is inferred. Each host period supplies the XTAL ticks in one complete period + * of source 0=SDK RTC slow mux, 1=RC_FAST/256, 2=XTAL32K. Caller supplies mux, + * gating, aligned full periods and subperiod phase. No CPU-time conversion. + * One-shot START rising edge clears RDY and accumulates MAX periods, then sets + * RDY; cycling (MAX=1 only) updates VALUE per period without one-shot RDY. VALUE retains + * the last completed count. Same-control writes preserve progress. Reconfiguring + * cycling restarts at the next full supplied period; physical phase/synchronizer + * latency is unqualified. START clears only by software. Stop preserves VALUE/RDY. + * Concurrent modes, multi-period cycling, active one-shot retarget/resize, + * active zero MAX, source 3, + * zero-tick intervals/25-bit overflow, TIMG1, oscillator startup/drift/electrical behavior and other + * timer/RTC/power/reset domains remain unsupported. Missed clock input stalls. + * Calls serialized between machine turns; failed operations preserve state. */ +typedef struct esk_esp32_rtc_cal { + uint32_t enabled,control,value,ready,active,periods,ticks; +} esk_esp32_rtc_cal; +void esk_esp32_rtc_cal_init(esk_esp32_rtc_cal *c); +/* on=1: control contains RW fields only, value is an explicit 25-bit snapshot. + * on=0 detaches. Snapshot creation is a host fixture, not silicon reset. */ +es_status esk_esp32_rtc_cal_configure(esk_esp32_rtc_cal *c,int on,uint32_t control,uint32_t value); +es_status esk_esp32_rtc_cal_read(const esk_esp32_rtc_cal *c,uint32_t address,unsigned bytes,uint32_t *value); +es_status esk_esp32_rtc_cal_write(esk_esp32_rtc_cal *c,uint32_t address,unsigned bytes,uint32_t value); +es_status esk_esp32_rtc_cal_period(esk_esp32_rtc_cal *c,unsigned source,uint32_t xtal_ticks); +#endif diff --git a/docs/STATUS.md b/docs/STATUS.md index df187f98d..697876c73 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -7,7 +7,16 @@ does not mean the described behaviour exists. ## Original ESP32 dual-core target investigation (2026-11-04) -**Latest ROM-map update:** an [explicit per-core SDK mask-ROM profile](engineering/esp32-mask-rom.md) +**Latest RTC-calibration update:** the [TIMG0 counter/controller profile](engineering/esp32-rtc-calibration.md) +now counts explicitly supplied XTAL ticks over selected-clock periods, with +one-shot completion and single-period cycling. 919,967 native assertions, +19,208 independent comparisons and 486 compiled C harness calls using unchanged +SDK access macros pass. The diagnostic with an additional explicit calibration +fixture reaches 6,872 retirements, then RTC_IO_DIG_PAD_HOLD read at 0x3ff48474. +The unclocked and synthetic-period cases are recorded separately; neither +verifies oscillator frequency, physical calibration, full SDK boot or CPU timing. + +**Prior ROM-map update:** an [explicit per-core SDK mask-ROM profile](engineering/esp32-mask-rom.md) now supplies the 448 KiB IROM view and coherent DROM tail alias using external PRO/APP images. 1,170 native assertions, 18,840 independent comparisons and 1,024 compiled C calls to two unchanged ROM getters pass. The PRO diagnostic diff --git a/docs/engineering/esp32-boot-verification.md b/docs/engineering/esp32-boot-verification.md index 619351b6d..1303e31f1 100644 --- a/docs/engineering/esp32-boot-verification.md +++ b/docs/engineering/esp32-boot-verification.md @@ -3,8 +3,8 @@ **Result on 2026-11-05: the owner's board reports ESP32-D0WD-V3 revision 3.1, JEDEC C4 60 16 and 4 MB flash. Giantec's GT25Q32A-U datasheet matches that ID; exact package/ordering suffix remains unconfirmed. The pinned SDK example boots -in Espressif QEMU. Native EmbSIM reaches timer calibration after 6,796 retired -instructions with explicit synthetic eFuse/RTC, active GPIO/UART-bank and mask-ROM fixtures; without an eFuse profile, +in Espressif QEMU. Native EmbSIM reaches per-pin pad hold after 6,872 retired +instructions with explicit synthetic eFuse/RTC/GPIO/UART-bank/mask-ROM/calibration fixtures; without an eFuse profile, the baseline stops at an eFuse read after seven.** Neither result establishes successful native boot. @@ -114,7 +114,18 @@ configuration or printed SDK capacity must not substitute for hardware identity. ## Native EmbSIM result: blocked -**Latest mask-ROM follow-up:** the [explicit per-core SDK mask-ROM profile](esp32-mask-rom.md) +**Latest RTC-calibration follow-up:** the [TIMG0 counter/controller](esp32-rtc-calibration.md) +uses explicit full-period XTAL tick inputs rather than inferred instruction time. +The additional `--rtc-cal-fixture` reaches 6,872 retirements / 6,873 attempts, +then RTC_IO_DIG_PAD_HOLD read at 0x3ff48474, PC 0x4000a67d, opcode 0x298. +By default it has no clock input and follows a ROM fallback; it does not verify +40 MHz detection. `--rtc-cal-initial-period-ticks 1280` supplies one synthetic +period and retains result 0x500. Both stop at the same next register and remain +boot unverified. [Unclocked evidence](../../tests/vendor/xtensa_integer/evidence/boot-with-rtc-cal-unclocked-2026-11-06.json) +and [period evidence](../../tests/vendor/xtensa_integer/evidence/boot-with-rtc-cal-period-2026-11-06.json) +record the fixture inputs separately; neither is a physical clock capture. + +**Prior mask-ROM follow-up:** the [explicit per-core SDK mask-ROM profile](esp32-mask-rom.md) resolves the extended IROM/DROM layout without changing the TRM-only default. With `--mask-rom-profile`, PRO retires 6,796 instructions / 6,797 attempts, then fails TIMG0 RTCCALICFG1 read at 0x3ff5f06c, PC 0x40008591, opcode 0x2222. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 3ffbaa514..9cdd70f24 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -51,7 +51,11 @@ currently exposed fabric window in that fixture. [Per-core SDK mask-ROM mapping](esp32-mask-rom.md) now supplies the extended IROM/DROM alias explicitly, preserving the default TRM view. Its additional fixture reaches 6,796 retirements, then TIMG0 RTCCALICFG1 read at 0x3ff5f06c. -No timer-calibration result or full SDK boot is claimed. +No physical timer-calibration result or full SDK boot is claimed. +[TIMG0 calibration](esp32-rtc-calibration.md) now adds an explicit period-driven +counter/controller. Its additional fixture reaches 6,872 retirements and the +RTC_IO_DIG_PAD_HOLD read at 0x3ff48474. Full clock-tree/oscillator and CPU timing +qualification remain separate work. [Instruction timing](esp32-instruction-timing.md) still requires physical evidence; a buildable capture firmware is prepared without claiming calibrated latency. It is not the owner's fuse dump and does not establish full boot. The [Giantec storage subset](esp32-giantec-profile.md) diff --git a/docs/engineering/esp32-rtc-calibration.md b/docs/engineering/esp32-rtc-calibration.md new file mode 100644 index 000000000..41428c901 --- /dev/null +++ b/docs/engineering/esp32-rtc-calibration.md @@ -0,0 +1,158 @@ +# Original ESP32 TIMG0 RTC calibration counter + +**Implemented: an opt-in TIMG0 calibration controller/counter with explicit +full-period XTAL-tick inputs, one-shot completion and single-period cycling. +This does not calibrate CPU instruction timing or implement the complete RTC +clock tree, oscillators, timer group or SDK boot.** + +## Real operation and supported sources + +ESP32 TRM v5.9, TIMG registers RTCCALICFG/RTCCALICFG1, defines calibration as +counting XTAL clock cycles over a selected number of calibrated-clock cycles. +The result is 25 bits and is read-only. READY is read-only and indicates one-shot +completion. The pinned ESP-IDF v4.4.7 `rtc_time.c` describes both one-off and +continuous operation; continuous operation is enabled at SoC reset. The TRM +labels START_CYCLING reserved, so this profile names the SDK protocol explicitly +rather than inferring additional undocumented modes. + +| Selection | SDK source contract | Input responsibility | +| --- | --- | --- | +| 0 | Currently selected RTC slow-clock mux | Caller supplies periods after mux selection | +| 1 | Internal RC_FAST divided by 256 | Caller supplies complete divided-clock periods | +| 2 | External XTAL32K | Caller supplies periods only when that clock exists/runs | + +The TRM describes selection 0 as RC_SLOW; the SDK calls it RTC_MUX and handles +other selected slow sources. This implementation uses the SDK's mux-level input +contract. It does not infer the mux, oscillator presence, gating, startup, +frequency, drift, voltage or temperature. The SDK's approximate oscillator +constants are not adopted as measurements of the owner's board. + +## Explicit state and observable registers + +`esk_esp32_machine_rtc_cal_configure` supplies enable, initial RW control fields +and an explicit prior 25-bit result. It creates a fixture with READY clear; it +is not a generic historical snapshot or a silicon reset producer. The documented +control reset fields form 0x13000 (MAX=1, source 1, cycling enabled). Supplying +result zero is an explicit initial condition, not inferred elapsed calibration +history. Detach removes this profile without pretending to power down the SoC. + +| Register | Address | Supported behavior | +| --- | --- | --- | +| TIMG0 RTCCALICFG | 0x3ff5f068 | START/MAX/source/cycling RW; READY RO; reserved bits ignored/zero | +| TIMG0 RTCCALICFG1 | 0x3ff5f06c | Completed VALUE in bits 31:7; writes ignored; low reserved bits zero | + +- One-shot supports MAX 1..32767. A START rising edge clears READY and resets + accumulated periods/ticks. Each selected complete period contributes its + supplied XTAL ticks. Only after MAX periods does VALUE change and READY set. + START remains a software-controlled bit; an unchanged write does not retrigger. +- Cycling is qualified only with MAX=1, matching the SDK's documented single- + period sampling. Every selected period updates VALUE; it does not assert the + one-shot READY latch. Larger cycling windows are unsupported until qualified. +- VALUE retains the last completed measurement during partial measurement, + source stalls, stops and reconfiguration. Repeated control writes preserve + progress. Software cannot forge READY or VALUE by writing RO fields. +- Stop clears pending progress and preserves the completed VALUE/READY latches. + A later measurement start clears READY. Retargeting/resizing a pending + one-shot without clearing START fails unchanged. Reconfiguring cycling + restarts at the next complete supplied period, retaining the last VALUE. + Physical in-flight phase and synchronizer latency remain unqualified. +- Selection 3, simultaneous START/cycling, active zero MAX, multi-period cycling, + zero-tick intervals and a result above 25 bits fail unchanged. Overflow wrapping/saturation and + undocumented ready behavior are not invented. Inactive MAX zero can be stored + while preparing controls; activating it is unsupported. +- Other TIMG registers and TIMG1 calibration remain unsupported. No completion + interrupt, watchdog, generic timer, hardware timeout or guessed delay is added. + +The SDK restricts its own requested period count to less than 32767 and checks +possible result overflow before starting. Supporting the register's full 15-bit +field does not claim the higher-level SDK accepts every register encoding. + +## Clock/event abstraction and integration + +`esk_esp32_machine_rtc_cal_period(source, xtal_ticks)` supplies one **complete +selected-clock period** and the integer XTAL ticks observed within it. The +caller aligns these records to the measurement window and supplies clock mux, +gating and phase behavior. Wrong-source records are ignored; missing records +leave calibration pending. Quantized intervals may alternate counts, so the +counter sums the supplied ticks instead of rounding an average frequency. +Reference-clock gating and active zero-tick intervals are unqualified; such +intervals fail unchanged. A stopped source supplies no complete period records. + +This API does not accept a fabricated final result or automatically convert CPU +retirements, CCOUNT, wall time or MMIO reads into clock progress. It is a functional +counter/controller driven by clock records, not an edge-level synchronizer or +physical oscillator model. Real captures or a separately qualified clock provider +are still required to establish real calibration timing/frequency. + +The original C99 counter is shared by both native CPU buses. A fetched START +store commits before subsequent host periods are supplied. Host events and calls +are serialized between machine turns; re-entry while executing fails. CPU-only +APP reset preserves the calibration state. Full power/watchdog/peripheral/global +reset domains and clock-enable routing are not qualified. Fetch/CAS and narrow +or misaligned MMIO accesses are unsupported. + +## Proof and limits of vendor evidence + +- **919,967 native assertions** check all three sources, one-shot sizes through + 32767, exact completion boundary, cycling, RO fields, stalls, source filtering, + rearm, stop, unsupported mutations/overflow with unchanged state, shared buses, + fetched START and CPU-reset retention. +- **19,208 independent period/result/READY comparisons** use pinned register + fields and independent sums for one-shot and repeated single-period cycling. +- **486 actual ESP32 C harness calls / 39,108 retired instructions** run on both + native CPUs. Ten unchanged SDK access macros operate the real register + addresses; the harness follows the documented setup/start/poll sequence. + Host periods arrive between execution turns, checking partial progress and + completion. A separate bounded-poll case receives no clock records and stays + pending despite firmware execution. No PC skip, memory callback implementing + the registers, fake oscillator or delay-function stub supplies a result. + +These are compiled harness calls using SDK macros, **not a claim that the full +`rtc_clk_cal_internal` implementation or its clock-enable/delay helpers run +unchanged**. Those depend on the remaining RTC clock tree and timing work. The +stalled harness checks pending state with bounded polls; it does not qualify the +SDK's microsecond timeout duration. Full SDK calibration, analog clocks and +instruction timing remain unverified. + +```sh +python3 tests/vendor/xtensa_integer/run_rtc_cal.py \ + --timer-reg "$IDF_PATH/components/soc/esp32/include/soc/timer_group_reg.h" \ + --soc-header "$IDF_PATH/components/soc/esp32/include/soc/soc.h" \ + --rtc-time "$IDF_PATH/components/esp_hw_support/port/esp32/rtc_time.c" \ + --compiler xtensa-esp32-elf-gcc --objcopy xtensa-esp32-elf-objcopy \ + --linker xtensa-esp32-elf-ld --nm xtensa-esp32-elf-nm +``` + +Inputs are pinned to +[ESP-IDF v4.4.7 / 38eeba213aa695aabfd6d89aa9f5078dbe5a94c3](https://github.com/espressif/esp-idf/tree/38eeba213aa695aabfd6d89aa9f5078dbe5a94c3). +Selected macros and register headers retain their source notices in temporary +build files. They are external test inputs; no vendor runtime is copied into the +simulator. The macro fixture selects the SDK's default inactive DPORT-assert +branch; calibration MMIO is outside DPORT. + +| Input | SHA-256 | +| --- | --- | +| `timer_group_reg.h` | `9aac43c44e42188bc7381e0936166383575c554157d2b087bf46d89b03d35e6c` | +| `soc.h` | `7a7148c288d7c289c961810e5617fa7b14d105b28349a45f772150b1e0845dd3` | +| `rtc_time.c` | `7542476747cd6175a7f846d3ac1af21914717f1278e3d9b0cdc5ab04c4f21883` | +| Source enum `soc/rtc.h` | `7a23243c95754644c449b093a9d8ee1422a18bf4032ce0875570faadb73b1a05` | + +## Native ROM investigation + +Add **`--rtc-cal-fixture`** to the existing diagnostic with explicit +eFuse/RTC/GPIO/UART-bank/mask-ROM fixtures. This supplies control 0x13000 and +prior value zero, with no clock periods by default. That unclocked case follows +a ROM fallback path; it does not prove detection of the owner's 40 MHz crystal. + +Optionally add **`--rtc-cal-initial-period-ticks 1280`** to supply one source-1 +period before PRO execution. The actual counter then records 1280 (0x500), and +the report includes its retained result. This is a synthetic test period, not a +physical capture, inferred oscillator frequency or instruction-time conversion. +No additional clock periods are generated automatically. + +Both fixtures now reach **6,872 retired instructions / 6,873 attempts**, stopping +at **RTC_IO_DIG_PAD_HOLD_REG read 0x3ff48474**, PC **0x4000a67d**, opcode **0x298**. +[Unclocked evidence](../../tests/vendor/xtensa_integer/evidence/boot-with-rtc-cal-unclocked-2026-11-06.json) +and [explicit-period evidence](../../tests/vendor/xtensa_integer/evidence/boot-with-rtc-cal-period-2026-11-06.json) +record the difference and retain `boot_verified: false`. Per-pad hold, complete +ROM/SDK boot, physical calibration and calibrated CPU timing remain unfinished. diff --git a/tests/unit/test_esp32_rtc_cal.c b/tests/unit/test_esp32_rtc_cal.c new file mode 100644 index 000000000..74c982101 --- /dev/null +++ b/tests/unit/test_esp32_rtc_cal.c @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/machine.h" +#define CFG UINT32_C(0x3ff5f068) +#define VALUE UINT32_C(0x3ff5f06c) +#define START UINT32_C(0x80000000) +static uint32_t read_reg(esk_esp32_rtc_cal *c,uint32_t address) +{ uint32_t v=0;CHECK_EQ(esk_esp32_rtc_cal_read(c,address,4,&v),ES_OK);return v; } +static void one_shot_sources_and_boundaries(void) +{ + const unsigned sizes[]={1,2,3,8,255,1024,32767};esk_esp32_rtc_cal c,before; + unsigned source,n,p;uint32_t expected,control,ticks; + esk_esp32_rtc_cal_init(&c); + for(source=0;source<3;source++) for(n=0;ncpu.a[2]=CFG;w->cpu.a[3]=START|(2u<<16);CHECK_EQ(esk_esp32_machine_run_core(m,0,1),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK);CHECK_EQ(event,0); + CHECK_EQ(esk_esp32_machine_rtc_cal_period(m,0,250),ES_OK); + CHECK_EQ(b[1].read(b[1].ctx,CFG,4,&v),ES_OK);CHECK_EQ(v&0x8000u,0); + CHECK_EQ(esk_esp32_machine_app_reset_configure(m,1),ES_OK); + CHECK_EQ(esk_esp32_machine_rtc_cal_period(m,0,251),ES_OK); + CHECK_EQ(b[1].read(b[1].ctx,VALUE,4,&v),ES_OK);CHECK_EQ(v,501u<<7); + CHECK_EQ(b[0].read(b[0].ctx,CFG,4,&v),ES_OK);CHECK_EQ(v&0x8000u,0x8000u); + CHECK_EQ(b[0].fetch(b[0].ctx,CFG,3,&v),ES_EUNSUPPORTED); + CHECK_EQ(b[0].compare_exchange(b[0].ctx,CFG,0,0,&v),ES_EUNSUPPORTED); + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +TEST_MAIN_BEGIN + (void)t_str_eq;RUN(one_shot_sources_and_boundaries);RUN(cycling_restart_stop_and_readonly); + RUN(overflow_invalid_requests_and_abort);RUN(native_bus_and_cpu_reset); +TEST_MAIN_END diff --git a/tests/vendor/README.md b/tests/vendor/README.md index 3aca17a9f..537b4c40b 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -258,3 +258,10 @@ alias observations and runs 1,024 compiled C calls to two unchanged ROM getter bodies using the actual SDK structure layout. It supplies distinct PRO/APP images and pins SDK/QEMU mapping inputs; this does not qualify physical ROM contents, compression, APP reset or SDK boot. [Mask-ROM contract](../../docs/engineering/esp32-mask-rom.md). + +`xtensa_integer/run_rtc_cal.py` checks 19,208 calibration period/result/READY +observations and runs 486 compiled C harness calls using ten unchanged SDK access +macros on both cores. Explicit clock records exercise one-shot completion, +single-period cycling and stalled state. This does not qualify the complete SDK +clock-enable/delay/calibration functions or physical frequency/timing. +[Counter/controller contract](../../docs/engineering/esp32-rtc-calibration.md). diff --git a/tests/vendor/xtensa_integer/evidence/boot-with-rtc-cal-period-2026-11-06.json b/tests/vendor/xtensa_integer/evidence/boot-with-rtc-cal-period-2026-11-06.json new file mode 100644 index 000000000..5d59711dd --- /dev/null +++ b/tests/vendor/xtensa_integer/evidence/boot-with-rtc-cal-period-2026-11-06.json @@ -0,0 +1,100 @@ +{ + "boot_verified": false, + "outcome": "blocked", + "core": "PRO only", + "rom_sha256": "9115bbcd03da9f54d3baca5ad94d588a43da54ed332347137ba3dc9010df258b", + "flash_sha256": "52fcca3c31ed835d45e22826d42dd7a7db0c867171fa330f9c56b0a81df7bee7", + "reset_vector": "0x40000400", + "memctl_profile": "bit 0 plus deterministic 256-byte loop-buffer fixture; not physical fetch/timing qualification", + "region_profile": "original ESP32 eight ITLB/DTLB identity regions, reset attribute 2, one ring, no Xtensa cache", + "prid_fixture": "0x0000cdcd", + "prid_reference": "ESP-IDF v4.4.7 xtensa_context.h documents PRO=0xcdcd, APP=0xabab; not a hardware readout", + "rom_tail_policy": "zero-fill absent DROM tail to match QEMU region setup, not silicon contents", + "flash_profile": "immutable SPI1 fixture, not a physical flash identity", + "rtc_cal_fixture": "explicit TIMG0 cycling control 0x13000, initial value 0; not measured oscillator or POR calibration history", + "rtc_cal_initial_period_ticks": 1280, + "rtc_cal_period_note": "one explicit source-1 full-period event before PRO execution, no automatic instruction-to-time conversion or physical frequency claim", + "uart_bank_fixture": "normal UART0/1/2, default disjoint shared FIFO allocations, empty FIFOs, explicit RXD=1 CTS=0 DSR=0 per port, no serial-clock advancement or board pinmux", + "gpio_fixture": "no routed pads; defined active-domain DIG_ISO defaults and hold controls only, no isolation/power emulation", + "rtc_fixture": "power-on cause 1 per core, vector selects 1, eight explicit zero retention words; not physical-owner RTC state", + "efuse_fixture_sha256": "2d8192ea7bdf3510bc117b4ea4b833a94b2ff2ceff252ce1610c297ed633d5bb", + "mask_rom_profile": "explicit PRO 448KiB SDK/QEMU IROM; final 64KiB also aliases DROM; external image tail zero-padded like reference, not measured silicon bytes", + "efuse_completion_fixture": "one external completion opportunity every 64 instruction attempts; not physical timing", + "status": -1, + "event": 0, + "pc": "0x4000a67d", + "opcode": "0x298", + "address": "0x3ff48474", + "fault_kind": 5, + "attempts": 6873, + "rtc_cal_value_after_stop": "0x500", + "region_readback": [ + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ], + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ] + ], + "region_access_attributes": [ + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ], + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ] + ], + "efuse_completions": 3, + "retired_before_stop": 6872, + "trace_tail": [ + "0x4000a484", + "0x4000a487", + "0x4000a48a", + "0x4000a48c", + "0x4000a492", + "0x4000a495", + "0x4000a498", + "0x4000a49a", + "0x4000a4e5", + "0x4000a4e8", + "0x4000a67a", + "0x4000a67d" + ], + "next_blocker": "RTC_IO_DIG_PAD_HOLD_REG read at 0x3ff48474, PC 0x4000a67d, opcode 0x298; per-pad hold and native SDK boot remain unverified", + "fixture_note": "Explicit counter/controller with no automatic clock progress. One synthetic source-1 period containing 1280 XTAL ticks, not a physical oscillator capture or inferred frequency.", + "sources_sha256": { + "core/mcu/esp32/rtc_cal.c": "6bb9d750d77d7085327db0f699b978fe75192ca9014a7df4c0c9d38c59bc31d1", + "core/mcu/esp32/rtc_cal.h": "4c2fea4d5f938794a6701d4fbb9a2c6f12d14fa2e9678a8b7e6a456d06d2c980", + "core/mcu/esp32/machine.c": "3513534a3d883f793ebf21c7a365bf70a37f51e4c38b95a310bc832468406adf", + "core/mcu/esp32/machine.h": "0b4e05bf12758144140eb6854663dbe7574f26bf4bfba4908b7cef392b902f7e", + "tests/vendor/xtensa_integer/probe_rom_boot.py": "cbdb1b740c388b90e224853ba873b977876cfc47bcef6ee15186cfde10f871d8" + } +} diff --git a/tests/vendor/xtensa_integer/evidence/boot-with-rtc-cal-unclocked-2026-11-06.json b/tests/vendor/xtensa_integer/evidence/boot-with-rtc-cal-unclocked-2026-11-06.json new file mode 100644 index 000000000..95f6639c2 --- /dev/null +++ b/tests/vendor/xtensa_integer/evidence/boot-with-rtc-cal-unclocked-2026-11-06.json @@ -0,0 +1,99 @@ +{ + "boot_verified": false, + "outcome": "blocked", + "core": "PRO only", + "rom_sha256": "9115bbcd03da9f54d3baca5ad94d588a43da54ed332347137ba3dc9010df258b", + "flash_sha256": "52fcca3c31ed835d45e22826d42dd7a7db0c867171fa330f9c56b0a81df7bee7", + "reset_vector": "0x40000400", + "memctl_profile": "bit 0 plus deterministic 256-byte loop-buffer fixture; not physical fetch/timing qualification", + "region_profile": "original ESP32 eight ITLB/DTLB identity regions, reset attribute 2, one ring, no Xtensa cache", + "prid_fixture": "0x0000cdcd", + "prid_reference": "ESP-IDF v4.4.7 xtensa_context.h documents PRO=0xcdcd, APP=0xabab; not a hardware readout", + "rom_tail_policy": "zero-fill absent DROM tail to match QEMU region setup, not silicon contents", + "flash_profile": "immutable SPI1 fixture, not a physical flash identity", + "rtc_cal_fixture": "explicit TIMG0 cycling control 0x13000, initial value 0; not measured oscillator or POR calibration history", + "rtc_cal_period_note": "no clock periods supplied; unclocked fixture exercises ROM fallback, not detection of owner 40MHz crystal", + "uart_bank_fixture": "normal UART0/1/2, default disjoint shared FIFO allocations, empty FIFOs, explicit RXD=1 CTS=0 DSR=0 per port, no serial-clock advancement or board pinmux", + "gpio_fixture": "no routed pads; defined active-domain DIG_ISO defaults and hold controls only, no isolation/power emulation", + "rtc_fixture": "power-on cause 1 per core, vector selects 1, eight explicit zero retention words; not physical-owner RTC state", + "efuse_fixture_sha256": "2d8192ea7bdf3510bc117b4ea4b833a94b2ff2ceff252ce1610c297ed633d5bb", + "mask_rom_profile": "explicit PRO 448KiB SDK/QEMU IROM; final 64KiB also aliases DROM; external image tail zero-padded like reference, not measured silicon bytes", + "efuse_completion_fixture": "one external completion opportunity every 64 instruction attempts; not physical timing", + "status": -1, + "event": 0, + "pc": "0x4000a67d", + "opcode": "0x298", + "address": "0x3ff48474", + "fault_kind": 5, + "attempts": 6873, + "rtc_cal_value_after_stop": "0x0", + "region_readback": [ + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ], + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ] + ], + "region_access_attributes": [ + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ], + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ] + ], + "efuse_completions": 3, + "retired_before_stop": 6872, + "trace_tail": [ + "0x4000a484", + "0x4000a487", + "0x4000a48a", + "0x4000a48c", + "0x4000a492", + "0x4000a495", + "0x4000a498", + "0x4000a49a", + "0x4000a4e5", + "0x4000a4e8", + "0x4000a67a", + "0x4000a67d" + ], + "next_blocker": "RTC_IO_DIG_PAD_HOLD_REG read at 0x3ff48474, PC 0x4000a67d, opcode 0x298; per-pad hold and native SDK boot remain unverified", + "fixture_note": "Explicit counter/controller with no automatic clock progress. No periods supplied; ROM fallback is not physical crystal-frequency verification.", + "sources_sha256": { + "core/mcu/esp32/rtc_cal.c": "6bb9d750d77d7085327db0f699b978fe75192ca9014a7df4c0c9d38c59bc31d1", + "core/mcu/esp32/rtc_cal.h": "4c2fea4d5f938794a6701d4fbb9a2c6f12d14fa2e9678a8b7e6a456d06d2c980", + "core/mcu/esp32/machine.c": "3513534a3d883f793ebf21c7a365bf70a37f51e4c38b95a310bc832468406adf", + "core/mcu/esp32/machine.h": "0b4e05bf12758144140eb6854663dbe7574f26bf4bfba4908b7cef392b902f7e", + "tests/vendor/xtensa_integer/probe_rom_boot.py": "cbdb1b740c388b90e224853ba873b977876cfc47bcef6ee15186cfde10f871d8" + } +} diff --git a/tests/vendor/xtensa_integer/probe_rom_boot.py b/tests/vendor/xtensa_integer/probe_rom_boot.py index 6b1f14723..adfe52c5b 100644 --- a/tests/vendor/xtensa_integer/probe_rom_boot.py +++ b/tests/vendor/xtensa_integer/probe_rom_boot.py @@ -16,7 +16,7 @@ from pathlib import Path import subprocess import tempfile -from run_execute import ROOT, Fault +from run_execute import ROOT, Fault, Memory from run_windows_c import Windows from run_machine_c import Allocator @@ -25,6 +25,8 @@ def main(): ap=argparse.ArgumentParser(description=__doc__) + ap.add_argument('--rtc-cal-initial-period-ticks',type=int,help='optional explicit source-1 full-period XTAL tick count before reset; requires rtc-cal fixture, not a frequency or physical measurement') + ap.add_argument('--rtc-cal-fixture',action='store_true',help='explicit TIMG0 cycling control 0x13000, prior value 0, no supplied clock periods; not physical oscillator/calibration history') ap.add_argument('--mask-rom-profile',action='store_true',help='explicit 448KiB SDK/QEMU IROM profile with DROM tail alias; zero-pad only the declared external-fixture tail') ap.add_argument('--rom',type=Path,required=True);ap.add_argument('--flash',type=Path,required=True) ap.add_argument('--efuse',type=Path,help='explicit JSON list of 31 raw None-coded fuse words; not inferred board defaults') @@ -37,6 +39,7 @@ def main(): ap.add_argument('--prid',type=lambda value:int(value,0),default=0xcdcd,help='PRO PRID; SDK documents 0xcdcd; 0 reproduces historical invalid-ID fixture') ap.add_argument('--report',type=Path);ap.add_argument('--steps',type=int,default=100000) ap.add_argument('--cc',default='cc');args=ap.parse_args() + if args.rtc_cal_initial_period_ticks is not None and (not args.rtc_cal_fixture or not 1<=args.rtc_cal_initial_period_ticks<=0x1ffffff):ap.error('initial calibration ticks require --rtc-cal-fixture and range 1..0x1ffffff') if not 1<=args.efuse_completion_period<=1000000:ap.error('--efuse-completion-period must be 1..1000000') if not 0<=args.prid<=0xffffffff:ap.error('--prid must be uint32') if not 1<=args.steps<=1000000:ap.error('--steps must be 1..1000000') @@ -46,7 +49,7 @@ def main(): with tempfile.TemporaryDirectory(prefix='embsim-native-rom-probe-') as tmp: lib=Path(tmp)/'probe.so' sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c', 'efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c', 'efuse.c','machine.c','rtc_cal.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT/'core/util/alloc.c'] subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC', '-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) @@ -56,6 +59,7 @@ def bind(name,result,*params): init=bind('esk_alloc_init',None,C.POINTER(Allocator),pointer) create=bind('esk_esp32_machine_create',C.c_int,C.POINTER(Allocator),C.POINTER(pointer)) destroy=bind('esk_esp32_machine_destroy',None,pointer) + bus_get=bind('esk_esp32_machine_bus',Memory,pointer,C.c_uint) fabric=bind('esk_esp32_machine_fabric',pointer,pointer) install=bind('esk_esp32_fabric_rom',C.c_int,pointer,C.c_uint,pointer,C.c_size_t) mask_rom=bind('esk_esp32_fabric_mask_rom',C.c_int,pointer,C.c_uint,pointer,C.c_size_t) @@ -67,6 +71,8 @@ def bind(name,result,*params): uart=bind('esk_esp32_machine_uart_configure',C.c_int,pointer,C.c_int,C.c_int,C.c_int,C.c_int) uart_port=bind('esk_esp32_machine_uart_port_configure',C.c_int,pointer,C.c_uint,C.c_int,C.c_int,C.c_int,C.c_int) gpio=bind('esk_esp32_machine_gpio_configure',C.c_int,pointer,C.POINTER(C.c_uint32),C.c_size_t) + calibration_period=bind('esk_esp32_machine_rtc_cal_period',C.c_int,pointer,C.c_uint,C.c_uint32) + calibration=bind('esk_esp32_machine_rtc_cal_configure',C.c_int,pointer,C.c_int,C.c_uint32,C.c_uint32) rtc=bind('esk_esp32_machine_rtc_configure',C.c_int,pointer,C.c_uint32,C.POINTER(C.c_uint32),C.c_size_t) memctl=bind('esk_xtensa_memctl_configure',C.c_int,C.POINTER(Windows)) region=bind('esk_xtensa_region_configure',C.c_int,C.POINTER(Windows)) @@ -85,6 +91,14 @@ def bind(name,result,*params): 'flash_profile':'immutable SPI1 fixture, not a physical flash identity'} try: ram=fabric(machine) + if args.rtc_cal_fixture: + assert calibration(machine,1,0x13000,0)==0 + report['rtc_cal_fixture']='explicit TIMG0 cycling control 0x13000, initial value 0; not measured oscillator or POR calibration history' + if args.rtc_cal_initial_period_ticks is not None: + assert calibration_period(machine,1,args.rtc_cal_initial_period_ticks)==0 + report['rtc_cal_initial_period_ticks']=args.rtc_cal_initial_period_ticks + report['rtc_cal_period_note']='one explicit source-1 full-period event before PRO execution, no automatic instruction-to-time conversion or physical frequency claim' + else:report['rtc_cal_period_note']='no clock periods supplied; unclocked fixture exercises ROM fallback, not detection of owner 40MHz crystal' if args.uart_bank_active_fixture: for port in range(3):assert uart_port(machine,port,1,1,0,0)==0 report['uart_bank_fixture']='normal UART0/1/2, default disjoint shared FIFO allocations, empty FIFOs, explicit RXD=1 CTS=0 DSR=0 per port, no serial-clock advancement or board pinmux' @@ -125,6 +139,10 @@ def bind(name,result,*params): pc=hex(fault.pc if status else pc),opcode=hex(fault.opcode),address=hex(fault.address), fault_kind=fault.kind,attempts=attempt+1) break + if args.rtc_cal_fixture: + bus=bus_get(machine,0);measured=C.c_uint32() + assert bus.read(bus.ctx,0x3ff5f06c,4,C.byref(measured))==0 + report['rtc_cal_value_after_stop']=hex(measured.value>>7) report['region_readback']=[list(bank) for bank in w.contents.region] report['region_access_attributes']=[list(bank) for bank in w.contents.region_active] report.update(efuse_completions=completions,retired_before_stop=retired(machine,0),trace_tail=list(trace)) diff --git a/tests/vendor/xtensa_integer/run_atomctl.py b/tests/vendor/xtensa_integer/run_atomctl.py index dc4dbbf47..c52e75b53 100644 --- a/tests/vendor/xtensa_integer/run_atomctl.py +++ b/tests/vendor/xtensa_integer/run_atomctl.py @@ -28,7 +28,7 @@ def main(): subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(obj),str(binary)],check=True) ops=binary.read_bytes();assert len(ops)==len(specs)*3 sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','rtc_cal.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] sources += [ROOT/'core/util/alloc.c'];lib=out/'atomctl.so' subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) api=C.CDLL(str(lib));pointer=C.c_void_p;wp=C.POINTER(Windows) diff --git a/tests/vendor/xtensa_integer/run_controls.py b/tests/vendor/xtensa_integer/run_controls.py index 49832498d..130d22fb7 100644 --- a/tests/vendor/xtensa_integer/run_controls.py +++ b/tests/vendor/xtensa_integer/run_controls.py @@ -38,7 +38,7 @@ def main(): with tempfile.TemporaryDirectory(prefix='embsim-controls-') as tmp: lib = Path(tmp) / 'controls.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','uart.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','rtc_cal.c','uart.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_efuse.py b/tests/vendor/xtensa_integer/run_efuse.py index a275daa2e..ebf120015 100644 --- a/tests/vendor/xtensa_integer/run_efuse.py +++ b/tests/vendor/xtensa_integer/run_efuse.py @@ -63,7 +63,7 @@ def get(value,name):mask,shift=field(name);return (value>>shift)&mask with tempfile.TemporaryDirectory(prefix='embsim-efuse-ref-') as tmp: out=Path(tmp);lib=out/'efuse.so' sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','rtc_cal.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] sources += [ROOT/'core/util/alloc.c'] subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) api=C.CDLL(str(lib));pointer=C.c_void_p diff --git a/tests/vendor/xtensa_integer/run_flash_c.py b/tests/vendor/xtensa_integer/run_flash_c.py index b5e23001f..26e31d6d6 100644 --- a/tests/vendor/xtensa_integer/run_flash_c.py +++ b/tests/vendor/xtensa_integer/run_flash_c.py @@ -68,7 +68,7 @@ def compile_code(name, source, base, entry, flags=()): flash_image = bytes(image) lib = out / 'flash.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','uart.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','rtc_cal.c','uart.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_gpio.py b/tests/vendor/xtensa_integer/run_gpio.py index f293557df..335cbdf08 100644 --- a/tests/vendor/xtensa_integer/run_gpio.py +++ b/tests/vendor/xtensa_integer/run_gpio.py @@ -53,7 +53,7 @@ def address(name):return 0x3ff44000+int(re.search(r'#define\s+GPIO_'+name+r'_REG with tempfile.TemporaryDirectory(prefix='embsim-gpio-c-') as tmp: out=Path(tmp);lib=out/'gpio.so' sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','rtc_cal.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] sources += [ROOT/'core/util/alloc.c'] subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) api=C.CDLL(str(lib));pointer=C.c_void_p diff --git a/tests/vendor/xtensa_integer/run_ipi_c.py b/tests/vendor/xtensa_integer/run_ipi_c.py index c0ea215b3..5f3a3700c 100644 --- a/tests/vendor/xtensa_integer/run_ipi_c.py +++ b/tests/vendor/xtensa_integer/run_ipi_c.py @@ -92,7 +92,7 @@ def main(): if len(f := line.split()) == 3} lib = out / 'ipi.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','uart.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','rtc_cal.c','uart.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_machine_c.py b/tests/vendor/xtensa_integer/run_machine_c.py index b9ce5f988..5d6ba28da 100644 --- a/tests/vendor/xtensa_integer/run_machine_c.py +++ b/tests/vendor/xtensa_integer/run_machine_c.py @@ -59,7 +59,7 @@ def main(): if len(f := line.split()) == 3} lib = out / 'machine.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','uart.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','rtc_cal.c','uart.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), diff --git a/tests/vendor/xtensa_integer/run_mask_rom.py b/tests/vendor/xtensa_integer/run_mask_rom.py index 4e8b5eb93..530a9f548 100644 --- a/tests/vendor/xtensa_integer/run_mask_rom.py +++ b/tests/vendor/xtensa_integer/run_mask_rom.py @@ -42,7 +42,7 @@ def main(): entries={name:int(re.search(r'PROVIDE\s*\(\s*'+name+r'\s*=\s*(0x[0-9a-fA-F]+)',symbols)[1],16) for name in helpers} with tempfile.TemporaryDirectory(prefix='embsim-mask-rom-') as tmp: out=Path(tmp);lib=out/'rom.so';sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','rtc_cal.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] sources += [ROOT/'core/util/alloc.c'] subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) api=C.CDLL(str(lib));pointer=C.c_void_p diff --git a/tests/vendor/xtensa_integer/run_memctl.py b/tests/vendor/xtensa_integer/run_memctl.py index 1a565b0c0..09ed34a7f 100644 --- a/tests/vendor/xtensa_integer/run_memctl.py +++ b/tests/vendor/xtensa_integer/run_memctl.py @@ -28,7 +28,7 @@ def main(): subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(obj),str(binary)],check=True) ops=binary.read_bytes();assert len(ops)==len(specs)*3 sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','rtc_cal.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] sources += [ROOT/'core/util/alloc.c'];lib=out/'memctl.so' subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) api=C.CDLL(str(lib));pointer=C.c_void_p;wp=C.POINTER(Windows) diff --git a/tests/vendor/xtensa_integer/run_regions.py b/tests/vendor/xtensa_integer/run_regions.py index 7c2120272..449447fa8 100644 --- a/tests/vendor/xtensa_integer/run_regions.py +++ b/tests/vendor/xtensa_integer/run_regions.py @@ -34,7 +34,7 @@ def main(): subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(obj),str(binary)],check=True) ops=binary.read_bytes();assert len(ops)==len(specs)*3 sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','rtc_cal.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] sources += [ROOT/'core/util/alloc.c'];lib=out/'regions.so' subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) api=C.CDLL(str(lib));pointer=C.c_void_p;wp=C.POINTER(Windows) diff --git a/tests/vendor/xtensa_integer/run_reset_c.py b/tests/vendor/xtensa_integer/run_reset_c.py index 0b644b61c..56ef4852e 100644 --- a/tests/vendor/xtensa_integer/run_reset_c.py +++ b/tests/vendor/xtensa_integer/run_reset_c.py @@ -117,7 +117,7 @@ def compile_image(name, source, base, entry, assembly=False): rom = rom + bytes(0x60000 - len(rom)) # original test fixture, not a product ROM lib = out / 'reset.so' sources = [ROOT / 'core/mcu/xtensa' / n for n in ['integer.c', 'execute.c', 'windows.c', 'interrupts.c']] - sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','uart.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] + sources += [ROOT / 'core/mcu/esp32' / n for n in ['fabric.c', 'efuse.c', 'machine.c','rtc_cal.c','uart.c','gpio.c', 'matrix.c', 'flashmap.c', 'flashspi.c', 'w25q32.c', 'nor32.c', 'gt25q32.c']] sources += [ROOT / 'core/util/alloc.c'] subprocess.run([args.cc, '-std=c99', '-Wall', '-Wextra', '-Wconversion', '-Werror', '-shared', '-fPIC', '-I' + str(ROOT / 'core'), '-I' + str(ROOT / 'include'), *map(str, sources), '-o', str(lib)], check=True) diff --git a/tests/vendor/xtensa_integer/run_rtc.py b/tests/vendor/xtensa_integer/run_rtc.py index 9569c371b..373e597f9 100644 --- a/tests/vendor/xtensa_integer/run_rtc.py +++ b/tests/vendor/xtensa_integer/run_rtc.py @@ -38,7 +38,7 @@ def field(name): reason=int(re.search(r'\bEXT_CPU_RESET\s*=\s*(\d+)',header)[1]);reset_reg=address('RESET_STATE') with tempfile.TemporaryDirectory(prefix='embsim-rtc-') as tmp: lib=Path(tmp)/'rtc.so';sources=[ROOT/'core/mcu/xtensa'/n for n in ['integer.c','execute.c','windows.c','interrupts.c']] - sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] + sources += [ROOT/'core/mcu/esp32'/n for n in ['fabric.c','efuse.c','machine.c','rtc_cal.c','uart.c','gpio.c','matrix.c','flashmap.c','flashspi.c','w25q32.c','nor32.c','gt25q32.c']] sources += [ROOT/'core/util/alloc.c'] subprocess.run([args.cc,'-std=c99','-Wall','-Wextra','-Wconversion','-Werror','-shared','-fPIC','-I'+str(ROOT/'core'),'-I'+str(ROOT/'include'),*map(str,sources),'-o',str(lib)],check=True) api=C.CDLL(str(lib));pointer=C.c_void_p diff --git a/tests/vendor/xtensa_integer/run_rtc_cal.py b/tests/vendor/xtensa_integer/run_rtc_cal.py new file mode 100644 index 000000000..f32e9e97a --- /dev/null +++ b/tests/vendor/xtensa_integer/run_rtc_cal.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""RTC calibration period referee and firmware using unchanged SDK access macros. + +Clock periods are explicit inputs. No oscillator frequency, SDK clock-tree +implementation, delay/timeout duration or physical timing is substituted. +""" +import argparse +import ctypes as C +from pathlib import Path +import random +import re +import subprocess +import tempfile +from run_execute import ROOT,Memory,Fault +from run_windows_c import Windows +from run_machine_c import Allocator +from run_matrix import read_header + + +def main(): + ap=argparse.ArgumentParser(description=__doc__) + for n in ['timer-reg','soc-header','rtc-time']:ap.add_argument('--'+n,type=Path,required=True) + for n in ['compiler','objcopy','linker','nm']:ap.add_argument('--'+n,required=True) + ap.add_argument('--cc',default='cc');args=ap.parse_args();rng=random.Random(0x3ff5f06c) + reg=read_header(args.timer_reg,'9aac43c44e42188bc7381e0936166383575c554157d2b087bf46d89b03d35e6c') + soc=read_header(args.soc_header,'7a7148c288d7c289c961810e5617fa7b14d105b28349a45f772150b1e0845dd3') + rtc_time=read_header(args.rtc_time,'7542476747cd6175a7f846d3ac1af21914717f1278e3d9b0cdc5ab04c4f21883') + assert 'In cycling mode' in rtc_time and 'In one-off mode' in rtc_time + def field(name):return int(re.search(r'#define TIMG_RTC_CALI_'+name+r'_S\s+(\d+)',reg)[1]) + def address(name):return 0x3ff5f000+int(re.search(r'#define TIMG_'+name+r'_REG\(i\)\s+\(REG_TIMG_BASE\(i\)\s*\+\s*(0x[0-9a-fA-F]+)',reg)[1],16) + cfg=address('RTCCALICFG');result=address('RTCCALICFG1');ready=1<\n#define BIT(n) (1U<<(n))\n'+base_line+'\n'+assertion+'\n'+macros+'\n',encoding='utf-8') + source='''/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "timer_group_reg.h" +static void prepare(unsigned n,unsigned source) { + CLEAR_PERI_REG_MASK(TIMG_RTCCALICFG_REG(0),TIMG_RTC_CALI_START); + REG_SET_FIELD(TIMG_RTCCALICFG_REG(0),TIMG_RTC_CALI_CLK_SEL,source); + CLEAR_PERI_REG_MASK(TIMG_RTCCALICFG_REG(0),TIMG_RTC_CALI_START_CYCLING); + REG_SET_FIELD(TIMG_RTCCALICFG_REG(0),TIMG_RTC_CALI_MAX,n); +} +unsigned work(unsigned n,unsigned source,unsigned mode) { + volatile unsigned *phase=(volatile unsigned *)0x3ffb9000; + prepare(n,source); + SET_PERI_REG_MASK(TIMG_RTCCALICFG_REG(0),TIMG_RTC_CALI_START); + if(mode) { + for(unsigned poll=0;poll<32;poll++) if(GET_PERI_REG_MASK(TIMG_RTCCALICFG_REG(0),TIMG_RTC_CALI_RDY)) return 0xbad00001; + return 0xffff0000; + } + *phase=1;while(*phase!=2) { } + if(GET_PERI_REG_MASK(TIMG_RTCCALICFG_REG(0),TIMG_RTC_CALI_RDY)) return 0xbad00002; + *phase=3;while(*phase!=4) { } + if(!GET_PERI_REG_MASK(TIMG_RTCCALICFG_REG(0),TIMG_RTC_CALI_RDY)) return 0xbad00003; + return REG_GET_FIELD(TIMG_RTCCALICFG1_REG(0),TIMG_RTC_CALI_VALUE); +} +unsigned cycling(unsigned n,unsigned source) { + volatile unsigned *phase=(volatile unsigned *)0x3ffb9000; + prepare(n,source); + SET_PERI_REG_MASK(TIMG_RTCCALICFG_REG(0),TIMG_RTC_CALI_START_CYCLING); + *phase=1;while(*phase!=2) { } + if(GET_PERI_REG_MASK(TIMG_RTCCALICFG_REG(0),TIMG_RTC_CALI_RDY)) return 0xbad00004; + unsigned first=REG_GET_FIELD(TIMG_RTCCALICFG1_REG(0),TIMG_RTC_CALI_VALUE); + *phase=3;while(*phase!=4) { } + if(GET_PERI_REG_MASK(TIMG_RTCCALICFG_REG(0),TIMG_RTC_CALI_RDY)) return 0xbad00005; + return first^REG_GET_FIELD(TIMG_RTCCALICFG1_REG(0),TIMG_RTC_CALI_VALUE); +} +''' + src=out/'work.c';obj=out/'work.o';elf=out/'work.elf';binary=out/'work.bin';src.write_text(source,encoding='utf-8') + subprocess.run([args.compiler,'-B'+str(Path(args.compiler).parent)+'/','-O1','-ffreestanding','-fno-builtin','-mtext-section-literals','-c',str(src),'-o',str(obj)],check=True) + subprocess.run([args.linker,'--no-relax','-Ttext=0x40080000','--entry=work',str(obj),'-o',str(elf)],check=True) + subprocess.run([args.objcopy,'-O','binary','--only-section=.text',str(elf),str(binary)],check=True) + symbols={f[2]:int(f[0],16) for line in subprocess.check_output([args.nm,str(elf)],text=True).splitlines() if len(f:=line.split())==3} + code=binary.read_bytes();assert load(fabric(m),0,0x40080000,C.c_char_p(code),len(code))==0 + assert bus.write(bus.ctx,0x3ff00030,4,1)==0 + for cpu in range(2): + for trial in range(243): + clock=trial%3;mode=1 if trial>=240 else 0;continuous=192<=trial<240;n=1 if continuous else 2+trial%31 + assert attach(m,1,0x13000,0x1234)==0 and bus.write(bus.ctx,0x3ffb9000,4,0)==0 + assert configure(m,cpu,symbols['cycling' if continuous else 'work'])==0;w=core(m,cpu).contents;w.ps|=1<<16 + w.cpu.a[1]=0x3ffd0000+cpu*4096;w.cpu.a[4]=0x7ffffff0;w.cpu.a[6]=n;w.cpu.a[7]=clock;w.cpu.a[8]=mode + assert run(m,cpu,1)==0;stage=0;ticks=[rng.randrange(1,8192) for _ in range(n)];other=[rng.randrange(1,8192) for _ in range(n)] + for turn in range(3000): + if w.cpu.pc==0x7ffffff0:break + assert step(m,cpu,C.byref(event),C.byref(fault))==0 and event.value==0,(hex(fault.pc),hex(fault.opcode),hex(fault.address),event.value) + instructions+=1 + phase=read(0x3ffb9000) + if phase==1 and stage==0: + for tick in ticks if continuous else ticks[:-1]:assert period(m,clock,tick)==0 + assert read(result)==((sum(ticks) if continuous else 0x1234)< Date: Thu, 5 Nov 2026 23:57:07 +0000 Subject: [PATCH 40/57] Implement ESP32 digital per-pin hold and combined pad latches --- CHANGELOG.md | 12 ++ core/mcu/esp32/gpio.c | 45 ++++-- core/mcu/esp32/gpio.h | 9 +- docs/STATUS.md | 10 +- docs/engineering/esp32-boot-verification.md | 12 +- docs/engineering/esp32-digital-pad-hold.md | 131 ++++++++++++++++++ docs/engineering/esp32-dual-core.md | 4 + docs/engineering/esp32-gpio-pad-hold.md | 9 +- docs/engineering/esp32-rtc-calibration.md | 4 + tests/unit/test_esp32_gpio_hold.c | 101 ++++++++++++++ tests/vendor/README.md | 6 + ...boot-with-digital-pad-hold-2026-11-06.json | 98 +++++++++++++ tests/vendor/xtensa_integer/run_gpio.py | 92 ++++++++++-- 13 files changed, 507 insertions(+), 26 deletions(-) create mode 100644 docs/engineering/esp32-digital-pad-hold.md create mode 100644 tests/unit/test_esp32_gpio_hold.c create mode 100644 tests/vendor/xtensa_integer/evidence/boot-with-digital-pad-hold-2026-11-06.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c889594a..687797dc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### Original ESP32 digital per-pin hold + +- Added the SDK-mapped digital hold register and per-pad capture masks. + Local/global/auto-hold causes combine without recapturing an already held + pad; FORCE_UNHOLD overrides them. Unrelated outputs remain live. +- Preserved captured output enable/level/open-drain and modeled input/pull + controls, live resolved input samples, raw register changes and CPU-reset + retention. Unsupported/package-specific bits fail without partial capture. +- 3,455 new native assertions, 4,096 independent transition turns and 256 compiled + C calls with eleven unchanged SDK helpers pass. Native ROM now reaches 6,902 + retirements, then IO_MUX U0TXD read; full pin routing/SDK boot remain unfinished. + ### Original ESP32 TIMG0 RTC calibration counter - Added explicit calibration snapshots, selected-period XTAL-tick counting, diff --git a/core/mcu/esp32/gpio.c b/core/mcu/esp32/gpio.c index 535ed621d..a15452e2b 100644 --- a/core/mcu/esp32/gpio.c +++ b/core/mcu/esp32/gpio.c @@ -5,13 +5,31 @@ #include static uint32_t valid(unsigned bank) { return bank ? 255u : UINT32_C(0x0effffff); } static uint32_t outputs(unsigned bank) { return bank ? 3u : valid(0); } +/* SDK GPIO_HOLD_MASK inverse; bit 13 is package-specific GPIO20, unqualified. + * These are pad-control bits, not GPIO-number shifts. */ +static const unsigned digital_hold_pin[17]={3,1,6,7,8,9,10,11,5,16,17,18,19,40,21,22,23}; +static uint32_t merge(uint32_t old,uint32_t live,uint32_t mask) +{ return (old&~mask)|(live&mask); } +static uint32_t projected(const esk_esp32_gpio *g,unsigned bank,const uint32_t live[2],const uint32_t held[2]) +{ return merge(live[bank],held[bank],g->held[bank]); } static void settle_hold(esk_esp32_gpio *g) { - uint32_t hold=(!(g->iso&0x4000u) && ((g->iso&0x8000u) || g->auto_hold)) ? 1u : 0u; - if(hold && !g->held) { - memcpy(g->hold_out,g->out,sizeof g->out);memcpy(g->hold_oe,g->oe,sizeof g->oe);memcpy(g->hold_od,g->od,sizeof g->od); + unsigned n,bank;uint32_t local[2]={0,0}; + int unhold=(g->iso&0x4000u)!=0,global=(g->iso&0x8000u)!=0 || g->auto_hold; + for(n=0;n<17;n++) if(digital_hold_pin[n]<40 && (g->dig_hold&(UINT32_C(1)<route[bank] : local[bank]&g->route[bank]); + uint32_t rising=next&~g->held[bank]; + g->hold_out[bank]=merge(g->hold_out[bank],g->out[bank],rising); + g->hold_oe[bank]=merge(g->hold_oe[bank],g->oe[bank],rising); + g->hold_od[bank]=merge(g->hold_od[bank],g->od[bank],rising); + g->hold_ie[bank]=merge(g->hold_ie[bank],g->ie[bank],rising); + g->hold_pu[bank]=merge(g->hold_pu[bank],g->pu[bank],rising); + g->hold_pd[bank]=merge(g->hold_pd[bank],g->pd[bank],rising); + g->held[bank]=next; } - g->held=hold; } void esk_esp32_gpio_init(esk_esp32_gpio *g) { @@ -56,11 +74,12 @@ es_status esk_esp32_gpio_output(const esk_esp32_gpio *g,unsigned pin,esk_esp32_g if(!g || !pad || pin>=40) return ES_EINVAL; if(!g->enabled || !selected(g,pin)) return ES_EUNSUPPORTED; bank=pin/32;bit=UINT32_C(1)<<(pin%32); - out=(g->held ? g->hold_out[bank] : g->out[bank])&bit; - oe=(g->held ? g->hold_oe[bank] : g->oe[bank])&outputs(bank)&bit; - od=(g->held ? g->hold_od[bank] : g->od[bank])&bit; + out=projected(g,bank,g->out,g->hold_out)&bit; + oe=projected(g,bank,g->oe,g->hold_oe)&outputs(bank)&bit; + od=projected(g,bank,g->od,g->hold_od)&bit; result.drive=oe && !(od && out) ? (out ? 1 : 0) : -1; - result.input_enabled=(g->ie[bank]&bit) ? 1u : 0u;result.pull_up=(g->pu[bank]&bit) ? 1u : 0u;result.pull_down=(g->pd[bank]&bit) ? 1u : 0u; + result.input_enabled=(projected(g,bank,g->ie,g->hold_ie)&bit) ? 1u : 0u; + result.pull_up=(projected(g,bank,g->pu,g->hold_pu)&bit) ? 1u : 0u;result.pull_down=(projected(g,bank,g->pd,g->hold_pd)&bit) ? 1u : 0u; *pad=result;return ES_OK; } es_status esk_esp32_gpio_auto_hold_event(esk_esp32_gpio *g) @@ -74,6 +93,7 @@ static es_status decode(const esk_esp32_gpio *g,uint32_t address,unsigned bytes, { if(!g->enabled) return ES_EUNSUPPORTED; if(address>=0x3ff44000 && address<0x3ff45000) *offset=address-0x3ff44000; + else if(address>=0x3ff48474 && address<0x3ff48478) *offset=0x1004; else if(address>=0x3ff48088 && address<0x3ff4808c) *offset=0x1000; else return ES_EUNSUPPORTED; if(bytes!=4 || (address&3u)) return ES_EINVAL; @@ -84,6 +104,7 @@ es_status esk_esp32_gpio_read(const esk_esp32_gpio *g,uint32_t address,unsigned uint32_t off;es_status st;unsigned pin,bank; if(!g || !value) return ES_EINVAL; st=decode(g,address,bytes,&off);if(st!=ES_OK) return st; + if(off==0x1004) { *value=g->dig_hold;return ES_OK; } if(off==0x1000) { *value=g->iso|(g->auto_hold<<9);return ES_OK; } if(off>=0x530 && off<=0x5cc) { pin=(off-0x530)/4;if(!selected(g,pin)) return ES_EUNSUPPORTED; @@ -98,8 +119,8 @@ es_status esk_esp32_gpio_read(const esk_esp32_gpio *g,uint32_t address,unsigned case 0x20:*value=g->oe[0];break;case 0x2c:*value=g->oe[1];break; case 0x3c:case 0x40: bank=off==0x40 ? 1u : 0u; - if(g->ie[bank]&~g->known[bank]) return ES_EUNSUPPORTED; - *value=g->level[bank]&g->ie[bank];break; + if(projected(g,bank,g->ie,g->hold_ie)&~g->known[bank]) return ES_EUNSUPPORTED; + *value=g->level[bank]&projected(g,bank,g->ie,g->hold_ie);break; default:return ES_EUNSUPPORTED; } return ES_OK; @@ -109,6 +130,10 @@ es_status esk_esp32_gpio_write(esk_esp32_gpio *g,uint32_t address,unsigned bytes uint32_t off,*reg,mask;unsigned bank,operation,pin;es_status st; if(!g) return ES_EINVAL; st=decode(g,address,bytes,&off);if(st!=ES_OK) return st; + if(off==0x1004) { + if(value&~UINT32_C(0x1dfff)) return ES_EUNSUPPORTED; + g->dig_hold=value;settle_hold(g);return ES_OK; + } if(off==0x1000) { /* Do not accept memory/Wi-Fi/power/pad-isolation changes without their * domain model. Reserved writes/RO status have no effect. */ diff --git a/core/mcu/esp32/gpio.h b/core/mcu/esp32/gpio.h index 99980e626..6010ff661 100644 --- a/core/mcu/esp32/gpio.h +++ b/core/mcu/esp32/gpio.h @@ -16,12 +16,17 @@ * DIG_ISO: global pad hold/unhold, auto-hold enable/latch/clear only. Isolation, * sleep/power/Wi-Fi/memory changes fail; fixed fields retain published defaults. * Auto-hold event is an external latch input, not SoC sleep execution. - * No IO_MUX/peripheral matrix, per-pad hold, IRQ/wakeup/filter/straps, sleep + * DIG_PAD_HOLD implements the SDK-mapped digital pins; RTC pad-specific hold + * and the PICO-only GPIO20 hold bit remain unsupported. Local/global hold use + * one effective latch per routed pad; FORCE_UNHOLD overrides local/auto hold. + * Simultaneous FORCE_HOLD/FORCE_UNHOLD remains unsupported. + * No IO_MUX/peripheral matrix, RTC per-pad hold, IRQ/wakeup/filter/straps, sleep * configuration, package mapping, loading/glitches/strength/timing qualification. * All calls serialized; failed operations preserve state/output. */ typedef struct esk_esp32_gpio { uint32_t enabled,route[2],ie[2],pu[2],pd[2],out[2],oe[2],od[2],known[2],level[2]; - uint32_t hold_out[2],hold_oe[2],hold_od[2],iso,auto_hold,held; + uint32_t hold_out[2],hold_oe[2],hold_od[2],hold_ie[2],hold_pu[2],hold_pd[2]; + uint32_t iso,auto_hold,dig_hold,held[2]; } esk_esp32_gpio; /* drive -1=HiZ, 0=low, 1=high. Pulls describe controls to an external solver. */ typedef struct esk_esp32_gpio_pad { int drive;unsigned input_enabled,pull_up,pull_down; } esk_esp32_gpio_pad; diff --git a/docs/STATUS.md b/docs/STATUS.md index 697876c73..c99339aa4 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -7,7 +7,15 @@ does not mean the described behaviour exists. ## Original ESP32 dual-core target investigation (2026-11-04) -**Latest RTC-calibration update:** the [TIMG0 counter/controller profile](engineering/esp32-rtc-calibration.md) +**Latest digital pad-hold update:** [Per-pin digital hold](engineering/esp32-digital-pad-hold.md) +now follows the SDK bit mapping and combines local/global/auto-hold causes with +FORCE_UNHOLD precedence and independent pad snapshots. 3,455 new native checks, +4,096 independent transition turns and 256 compiled C calls using eleven unchanged +SDK helpers pass. Native ROM now reaches 6,902 retirements and IO_MUX U0TXD read +at 0x3ff49088. RTC pad-specific hold, IO_MUX/peripheral routing, physical board +behavior and complete SDK boot remain unqualified. + +**Prior RTC-calibration update:** the [TIMG0 counter/controller profile](engineering/esp32-rtc-calibration.md) now counts explicitly supplied XTAL ticks over selected-clock periods, with one-shot completion and single-period cycling. 919,967 native assertions, 19,208 independent comparisons and 486 compiled C harness calls using unchanged diff --git a/docs/engineering/esp32-boot-verification.md b/docs/engineering/esp32-boot-verification.md index 1303e31f1..86ae09148 100644 --- a/docs/engineering/esp32-boot-verification.md +++ b/docs/engineering/esp32-boot-verification.md @@ -3,7 +3,7 @@ **Result on 2026-11-05: the owner's board reports ESP32-D0WD-V3 revision 3.1, JEDEC C4 60 16 and 4 MB flash. Giantec's GT25Q32A-U datasheet matches that ID; exact package/ordering suffix remains unconfirmed. The pinned SDK example boots -in Espressif QEMU. Native EmbSIM reaches per-pin pad hold after 6,872 retired +in Espressif QEMU. Native EmbSIM reaches IO_MUX U0TXD after 6,902 retired instructions with explicit synthetic eFuse/RTC/GPIO/UART-bank/mask-ROM/calibration fixtures; without an eFuse profile, the baseline stops at an eFuse read after seven.** Neither result establishes successful native boot. @@ -114,7 +114,15 @@ configuration or printed SDK capacity must not substitute for hardware identity. ## Native EmbSIM result: blocked -**Latest RTC-calibration follow-up:** the [TIMG0 counter/controller](esp32-rtc-calibration.md) +**Latest digital pad-hold follow-up:** [The digital pin hold register](esp32-digital-pad-hold.md) +now supplies SDK-mapped controls and observable held outputs with local/global +composition. The diagnostic reaches 6,902 retirements / 6,903 attempts, then +IO_MUX U0TXD read 0x3ff49088, PC 0x40009156, opcode 0x498. This uses the same +explicit fixture inputs including one synthetic 1,280-tick calibration period. +[New evidence](../../tests/vendor/xtensa_integer/evidence/boot-with-digital-pad-hold-2026-11-06.json) +retains boot unverified; peripheral pin routing is not replaced by register stubs. + +**Prior RTC-calibration follow-up:** the [TIMG0 counter/controller](esp32-rtc-calibration.md) uses explicit full-period XTAL tick inputs rather than inferred instruction time. The additional `--rtc-cal-fixture` reaches 6,872 retirements / 6,873 attempts, then RTC_IO_DIG_PAD_HOLD read at 0x3ff48474, PC 0x4000a67d, opcode 0x298. diff --git a/docs/engineering/esp32-digital-pad-hold.md b/docs/engineering/esp32-digital-pad-hold.md new file mode 100644 index 000000000..3f60f9741 --- /dev/null +++ b/docs/engineering/esp32-digital-pad-hold.md @@ -0,0 +1,131 @@ +# Original ESP32 digital per-pin hold + +**Implemented: SDK-mapped digital per-pin hold, combined local/global/auto-hold +latching, FORCE_UNHOLD override and live resolved input levels. This remains a +software-GPIO route profile, not complete RTC-pad/IO_MUX/peripheral pin routing, +electrical loading, power/reset domains or successful SDK boot.** + +## Hardware mapping and supported pins + +ESP32 TRM v5.9 sections 6.7 and 6.13 describe pin hold: local and global hold +signals combine, and the pad retains its configuration while held. The SDK +`GPIO_HOLD_MASK` table supplies the digital control mapping. Hold bits are not +GPIO-number shifts. + +| Register bit | GPIO | +| --- | --- | +| 0 / 1 | 3 (U0RXD) / 1 (U0TXD) | +| 2 / 3 / 4 / 5 / 6 / 7 | 6 / 7 / 8 / 9 / 10 / 11 | +| 8 / 9 / 10 / 11 / 12 | 5 / 16 / 17 / 18 / 19 | +| 14 / 15 / 16 | 21 / 22 / 23 | + +`RTC_IO_DIG_PAD_HOLD_REG`, **0x3ff48474**, initializes to its published zero +control value when the explicit GPIO fixture attaches. Supported writes update +the mapped bits together. Bit 13 refers to package-specific GPIO20 in the TRM, +but the pinned SDK hold table leaves GPIO20 unsupported. It and bits 17..31 +therefore fail unchanged when set. RTC-capable pads use other hold registers; +those are not silently mapped onto this digital register. The table does not +establish which pads are exposed on the owner's development board. + +## Effective hold and observable behavior + +The [software-GPIO/global-hold contract](esp32-gpio-pad-hold.md) still defines +explicit routing and initial controls. The new register adds digital local +hold. For each routed pad: + +- FORCE_UNHOLD disables local/auto hold even when their controls remain set. + Simultaneous FORCE_HOLD/FORCE_UNHOLD remains unsupported, as in the baseline. The fixture's published DIG_ISO default has FORCE_UNHOLD set; the active + ROM clears it before ordinary pad-hold use. Test setup explicitly releases it. +- Otherwise, local hold, FORCE_HOLD and the armed auto-hold latch combine with + OR. A transition from unheld to held captures the current modeled controls. +- Adding another hold cause to an already held pad does not recapture changed + live registers. Removing one cause does not release a pad while another cause + remains. Removing FORCE_UNHOLD while a cause is pending captures current live + controls at the new effective hold edge. +- OUT/ENABLE/PAD_DRIVER writes remain readable as live register values while + pad output projection retains captured level, enable and open-drain state. + Release exposes current live controls, including HiZ when disabled or an + open-drain high releases the output. Unrelated pads remain independently live. +- Modeled input-enable and pull controls are captured consistently too. These + route attributes are fixed in the current fixture; runtime IO_MUX mutation, + function switching and drive-strength changes remain unsupported. +- Hold freezes controls, not external input samples. An enabled held input can + still read changes from its externally resolved level, as the SDK documents. + An unresolved enabled input still fails unchanged instead of becoming zero. + +One effective mask and one snapshot per routed pad implement this behavior; +local/global snapshots do not compete for priority. Writes selecting known but +unrouted pad controls preserve the register latch without fabricating a pad +state: output queries for those endpoints remain unsupported. Host re-attach is +fixture setup, not a hardware reset or routing change that preserves old holds. + +The register is shared by both native CPU bus views. Fetched hold stores take +effect through the normal serialized bus/commit path. The bounded CPU-only reset +preserves holds and snapshots. Other resets, Deep-sleep transitions, isolation, +physical glitch/synchronizer timing, peripheral signal paths and electrical IV +behavior remain unqualified. In particular, software-route pin tests do not +qualify flash/UART operation through held peripheral pins. + +MMIO is aligned 32-bit data only. Unknown bits/features, narrow/misaligned access, +fetch and CAS remain unsupported. Invalid multi-bit requests preserve the entire +register and all captures; no supported bit is partially applied first. + +## Proof and reproduction + +- **3,455 new native assertions** cover all 16 mapped pads across enable, level + and open-drain combinations, independent neighbors, live input-mode levels, + pull/input projections, overlapping local/global/auto-hold causes, force-unhold, + recapture after override, unsupported bits/unchanged failure, floating inputs, + fetched stores, shared CPU buses and CPU-reset retention. +- The original **3,313 GPIO assertions** and the default vendor runner's + 216 C calls / 25,380 retired instructions remain passing. +- The extended runner retains **2,048 latch/pad comparisons** and adds **4,096 + independent per-pin/global transition turns**. Its referee parses the actual + SDK masks, tracks live controls and per-pad snapshots separately, and checks + projected outputs plus raw register/status/input observations across mixed + local/global/unhold/auto-hold events. +- **256 actual ESP32 C interactive calls / 30,592 retired instructions** run on + both native CPUs using **eleven unchanged SDK helper bodies**. The C program + uses the SDK's real hold-mask array, enables a pin hold, changes output controls, + reads a live button/input, then releases the hold. Host events add/remove a + global hold while the local hold remains set. Output and input-only operating + modes are checked. No host PC shortcut or Python instruction-memory callback + implements the GPIO operations. + +Add the following to the [existing GPIO reproduction command](esp32-gpio-pad-hold.md#evidence-and-reproduction): + +```sh +--per-pin-hold \ +--rtc-io-reg "$IDF_PATH/components/soc/esp32/include/soc/rtc_io_reg.h" \ +--gpio-periph "$IDF_PATH/components/soc/esp32/gpio_periph.c" +``` + +The default command keeps its original scope. In the extended mode the linker +places the SDK mapping constant in the test image's read-only data section, and +the fixture loads both `.text` and `.rodata` into the supplied IRAM image. The +native CPU reads the real compiled lookup table; the host does not replace it. +Selected helper bodies, mapping data and headers retain their source notices in +temporary build files. No vendor runtime is bundled in the simulator. + +The original GPIO/hold source hashes remain pinned to ESP-IDF v4.4.7 commit +[38eeba213aa695aabfd6d89aa9f5078dbe5a94c3](https://github.com/espressif/esp-idf/tree/38eeba213aa695aabfd6d89aa9f5078dbe5a94c3). +Additional inputs: + +| Source | SHA-256 | +| --- | --- | +| `soc/esp32/include/soc/rtc_io_reg.h` | `a2936c43b3735fae816b7f4c4b76c76ce90694a50bba56d310e36379cf5f1119` | +| `soc/esp32/gpio_periph.c` | `bef42aa91bbea259885a60a535a8fd89fa4137b4c2af3d8ba8ac9b547032329a` | +| Public GPIO behavior `driver/include/driver/gpio.h` | `0cd2f0ad1aaaba56e452943ff1c8022d07d00b707b93b368d5539fa759a193a2` | + +## Native boot investigation + +The existing GPIO fixture now includes this qualified register, so the diagnostic +passes the prior 6,872-instruction pad-hold blocker. With the same explicit +eFuse/RTC/GPIO/UART-bank/mask-ROM/calibration inputs and one synthetic 1,280-tick +period, it reaches **6,902 retired instructions / 6,903 attempts**, then fails +**IO_MUX U0TXD read 0x3ff49088**, PC **0x40009156**, opcode **0x498**. +[Hash-recorded evidence](../../tests/vendor/xtensa_integer/evidence/boot-with-digital-pad-hold-2026-11-06.json) +retains `boot_verified: false`. The previous 6,872 result remains recorded against +its earlier source hashes; it is superseded by this implementation, not relabeled +as successful boot. UART pin routing/IO_MUX, physical board behavior, full SDK +boot and calibrated instruction timing remain unfinished. diff --git a/docs/engineering/esp32-dual-core.md b/docs/engineering/esp32-dual-core.md index 9cdd70f24..e4b0ea55e 100644 --- a/docs/engineering/esp32-dual-core.md +++ b/docs/engineering/esp32-dual-core.md @@ -56,6 +56,10 @@ No physical timer-calibration result or full SDK boot is claimed. counter/controller. Its additional fixture reaches 6,872 retirements and the RTC_IO_DIG_PAD_HOLD read at 0x3ff48474. Full clock-tree/oscillator and CPU timing qualification remain separate work. +[Digital per-pin hold](esp32-digital-pad-hold.md) now adds independent snapshots +and local/global/unhold composition. The native diagnostic reaches 6,902 +retirements, then IO_MUX U0TXD read at 0x3ff49088. Full IO_MUX/peripheral routing +and native SDK boot remain unqualified. [Instruction timing](esp32-instruction-timing.md) still requires physical evidence; a buildable capture firmware is prepared without claiming calibrated latency. It is not the owner's fuse dump and does not establish full boot. The [Giantec storage subset](esp32-giantec-profile.md) diff --git a/docs/engineering/esp32-gpio-pad-hold.md b/docs/engineering/esp32-gpio-pad-hold.md index adc624a59..0c58c98e4 100644 --- a/docs/engineering/esp32-gpio-pad-hold.md +++ b/docs/engineering/esp32-gpio-pad-hold.md @@ -5,6 +5,10 @@ resolved digital inputs, push-pull/open-drain/HiZ output projection and the glob pad-hold subset of DIG_ISO. This is not complete GPIO/IO_MUX, physical pad loading, power isolation, package/board integration or SDK boot.** +The [digital per-pin follow-up](esp32-digital-pad-hold.md) now adds the SDK-mapped +local hold register and combines all hold causes per pad. This page retains the +original global-hold baseline and its reproducible vendor evidence. + ## Register identity correction The earlier RTC evidence mislabeled 0x3ff48088 as ANA_CONF. The pinned ESP-IDF @@ -53,7 +57,7 @@ physical pad. Detach/reconfigure are host fixture operations, not silicon reset. high-bank input reads are separately checked. MMIO is aligned 32-bit only. Unknown GPIO registers, IO_MUX changes, matrix -peripheral routes, per-pad hold, IRQ/wakeup/filter/strap/sleep configuration and +peripheral routes, RTC per-pad hold, IRQ/wakeup/filter/strap/sleep configuration and pad IV/voltage/glitch/timing behavior remain unsupported. This profile is an unregistered experimental backend, not a graphical board-pin binding. @@ -69,7 +73,8 @@ When hold becomes effective, current output level, enable and open-drain control are captured. Subsequent register writes remain readable but do not alter the projected held output. Release exposes current controls. FORCE_UNHOLD disables hold; simultaneous FORCE_HOLD/FORCE_UNHOLD is explicitly unsupported because its -priority is unqualified. Capture is global across selected endpoints. +priority is unqualified. Global capture covers selected endpoints; local hold +now shares the same effective per-pad latch, as specified in the follow-up. The auto-hold event is an explicit external latch input and only sets the latch when armed. It is **not** a complete SoC Deep-sleep transition: the CPU, clocks, diff --git a/docs/engineering/esp32-rtc-calibration.md b/docs/engineering/esp32-rtc-calibration.md index 41428c901..9a753b0cf 100644 --- a/docs/engineering/esp32-rtc-calibration.md +++ b/docs/engineering/esp32-rtc-calibration.md @@ -139,6 +139,10 @@ branch; calibration MMIO is outside DPORT. ## Native ROM investigation +The following records preserve the calibration milestone. The +[digital pad-hold follow-up](esp32-digital-pad-hold.md) now passes its former +next blocker; the latest boot boundary is recorded there. + Add **`--rtc-cal-fixture`** to the existing diagnostic with explicit eFuse/RTC/GPIO/UART-bank/mask-ROM fixtures. This supplies control 0x13000 and prior value zero, with no clock periods by default. That unclocked case follows diff --git a/tests/unit/test_esp32_gpio_hold.c b/tests/unit/test_esp32_gpio_hold.c new file mode 100644 index 000000000..ef9ef3221 --- /dev/null +++ b/tests/unit/test_esp32_gpio_hold.c @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +#include "harness.h" +#include "mcu/esp32/machine.h" +#define GPIO UINT32_C(0x3ff44000) +#define ISO UINT32_C(0x3ff48088) +#define HOLD UINT32_C(0x3ff48474) +static const unsigned pins[]={3,1,6,7,8,9,10,11,5,16,17,18,19,21,22,23}; +static const unsigned bits[]={0,1,2,3,4,5,6,7,8,9,10,11,12,14,15,16}; +static void write_reg(esk_esp32_gpio *g,uint32_t a,uint32_t v) +{ CHECK_EQ(esk_esp32_gpio_write(g,a,4,v),ES_OK); } +static uint32_t read_reg(esk_esp32_gpio *g,uint32_t a) +{ uint32_t v=0;CHECK_EQ(esk_esp32_gpio_read(g,a,4,&v),ES_OK);return v; } +static int drive(esk_esp32_gpio *g,unsigned pin) +{ esk_esp32_gpio_pad p;CHECK_EQ(esk_esp32_gpio_output(g,pin,&p),ES_OK);return p.drive; } +static void all_mapped_pads_and_live_inputs(void) +{ + esk_esp32_gpio g;esk_esp32_gpio_pad pad;uint32_t cfg[14],bit,otherbit,mask;unsigned n,oe,od,high,other; + for(n=0;n<16;n++) for(oe=0;oe<2;oe++) for(od=0;od<2;od++) for(high=0;high<2;high++) { + bit=UINT32_C(1)<16) { + CHECK_EQ(esk_esp32_gpio_write(&g,HOLD,4,2u|(UINT32_C(1)<cpu.a[2]=HOLD;w->cpu.a[3]=2;CHECK_EQ(esk_esp32_machine_run_core(m,0,1),ES_OK); + CHECK_EQ(esk_esp32_machine_step(m,0,&event,&fault),ES_OK);CHECK_EQ(event,0); + CHECK_EQ(bus[1].read(bus[1].ctx,HOLD,4,&v),ES_OK);CHECK_EQ(v,2); + CHECK_EQ(bus[1].write(bus[1].ctx,GPIO+4,4,2),ES_OK);CHECK_EQ(esk_esp32_machine_gpio_output(m,1,&pad),ES_OK);CHECK_EQ(pad.drive,0); + CHECK_EQ(esk_esp32_machine_app_reset_configure(m,1),ES_OK); + CHECK_EQ(esk_esp32_machine_gpio_output(m,1,&pad),ES_OK);CHECK_EQ(pad.drive,0); + CHECK_EQ(bus[0].fetch(bus[0].ctx,HOLD,3,&v),ES_EUNSUPPORTED); + CHECK_EQ(bus[0].compare_exchange(bus[0].ctx,HOLD,0,0,&v),ES_EUNSUPPORTED); + CHECK_EQ(bus[1].write(bus[1].ctx,HOLD,4,0),ES_OK); + CHECK_EQ(esk_esp32_machine_gpio_output(m,1,&pad),ES_OK);CHECK_EQ(pad.drive,1); + esk_esp32_machine_destroy(m);CHECK_EQ(a.live_bytes,0); +} +TEST_MAIN_BEGIN + (void)t_str_eq;RUN(all_mapped_pads_and_live_inputs);RUN(overlapping_causes_and_force_unhold); + RUN(unsupported_bits_and_unresolved_inputs);RUN(fetched_hold_shared_buses_and_reset); +TEST_MAIN_END diff --git a/tests/vendor/README.md b/tests/vendor/README.md index 537b4c40b..8f0c1e4ee 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -265,3 +265,9 @@ macros on both cores. Explicit clock records exercise one-shot completion, single-period cycling and stalled state. This does not qualify the complete SDK clock-enable/delay/calibration functions or physical frequency/timing. [Counter/controller contract](../../docs/engineering/esp32-rtc-calibration.md). + +`xtensa_integer/run_gpio.py --per-pin-hold` adds 4,096 independent transition +turns and 256 compiled C calls using eleven unchanged SDK helpers and the actual +hold-mask table. It checks local/global overlap, force-unhold, open-drain/enable +capture and live inputs. RTC pad controls, peripheral routing and electrical +behavior remain unqualified. [Digital per-pin contract](../../docs/engineering/esp32-digital-pad-hold.md). diff --git a/tests/vendor/xtensa_integer/evidence/boot-with-digital-pad-hold-2026-11-06.json b/tests/vendor/xtensa_integer/evidence/boot-with-digital-pad-hold-2026-11-06.json new file mode 100644 index 000000000..b0e6dc545 --- /dev/null +++ b/tests/vendor/xtensa_integer/evidence/boot-with-digital-pad-hold-2026-11-06.json @@ -0,0 +1,98 @@ +{ + "boot_verified": false, + "outcome": "blocked", + "core": "PRO only", + "rom_sha256": "9115bbcd03da9f54d3baca5ad94d588a43da54ed332347137ba3dc9010df258b", + "flash_sha256": "52fcca3c31ed835d45e22826d42dd7a7db0c867171fa330f9c56b0a81df7bee7", + "reset_vector": "0x40000400", + "memctl_profile": "bit 0 plus deterministic 256-byte loop-buffer fixture; not physical fetch/timing qualification", + "region_profile": "original ESP32 eight ITLB/DTLB identity regions, reset attribute 2, one ring, no Xtensa cache", + "prid_fixture": "0x0000cdcd", + "prid_reference": "ESP-IDF v4.4.7 xtensa_context.h documents PRO=0xcdcd, APP=0xabab; not a hardware readout", + "rom_tail_policy": "zero-fill absent DROM tail to match QEMU region setup, not silicon contents", + "flash_profile": "immutable SPI1 fixture, not a physical flash identity", + "rtc_cal_fixture": "explicit TIMG0 cycling control 0x13000, initial value 0; not measured oscillator or POR calibration history", + "rtc_cal_initial_period_ticks": 1280, + "rtc_cal_period_note": "one explicit source-1 full-period event before PRO execution, no automatic instruction-to-time conversion or physical frequency claim", + "uart_bank_fixture": "normal UART0/1/2, default disjoint shared FIFO allocations, empty FIFOs, explicit RXD=1 CTS=0 DSR=0 per port, no serial-clock advancement or board pinmux", + "gpio_fixture": "no routed pads; defined active-domain DIG_ISO defaults and hold controls only, no isolation/power emulation", + "rtc_fixture": "power-on cause 1 per core, vector selects 1, eight explicit zero retention words; not physical-owner RTC state", + "efuse_fixture_sha256": "2d8192ea7bdf3510bc117b4ea4b833a94b2ff2ceff252ce1610c297ed633d5bb", + "mask_rom_profile": "explicit PRO 448KiB SDK/QEMU IROM; final 64KiB also aliases DROM; external image tail zero-padded like reference, not measured silicon bytes", + "efuse_completion_fixture": "one external completion opportunity every 64 instruction attempts; not physical timing", + "status": -1, + "event": 0, + "pc": "0x40009156", + "opcode": "0x498", + "address": "0x3ff49088", + "fault_kind": 5, + "attempts": 6903, + "rtc_cal_value_after_stop": "0x500", + "region_readback": [ + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ], + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ] + ], + "region_access_attributes": [ + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ], + [ + 15, + 1, + 1, + 2, + 2, + 2, + 2, + 2 + ] + ], + "efuse_completions": 3, + "retired_before_stop": 6902, + "trace_tail": [ + "0x4000a69a", + "0x4000a69d", + "0x4000a69f", + "0x4000a6a1", + "0x4000a724", + "0x4000a727", + "0x4000a72a", + "0x4000a72c", + "0x4000914d", + "0x40009150", + "0x40009153", + "0x40009156" + ], + "next_blocker": "IO_MUX U0TXD read at 0x3ff49088, PC 0x40009156, opcode 0x498; full pin routing and native SDK boot remain unverified", + "fixture_note": "Same explicit software-GPIO/RTC/UART/mask-ROM/calibration fixtures; per-pin digital hold now modeled. Synthetic calibration period is not a physical clock capture.", + "sources_sha256": { + "core/mcu/esp32/gpio.c": "6567b5ffe04910277d5ff8c0d9bdc7c1fc380e5d7bddcead4c0eded9f2c603da", + "core/mcu/esp32/gpio.h": "f245e15921160a561cd429353473cf29f25bd5054aa2a13f890de628947bc94b", + "tests/vendor/xtensa_integer/probe_rom_boot.py": "cbdb1b740c388b90e224853ba873b977876cfc47bcef6ee15186cfde10f871d8" + } +} diff --git a/tests/vendor/xtensa_integer/run_gpio.py b/tests/vendor/xtensa_integer/run_gpio.py index 335cbdf08..8470a59cf 100644 --- a/tests/vendor/xtensa_integer/run_gpio.py +++ b/tests/vendor/xtensa_integer/run_gpio.py @@ -23,11 +23,58 @@ class Pad(C.Structure): _fields_=[('drive',C.c_int)]+[(n,C.c_uint) for n in ['ie','pu','pd']] +def per_pin_oracle(m,bus,configure,inputs,output,event,mapping,rng): + """Independent pad snapshots, using SDK pin masks and visible controls.""" + pins=[p for p,b in enumerate(mapping) if b]+[2] + settings=(C.c_uint32*14)();settings[0]=sum(1<

>p)&1 + elif op==1: + oe=rng.getrandbits(32);assert bus.write(bus.ctx,0x3ff44020,4,oe)==0 + for p in pins:live[p][1]=(oe>>p)&1 + elif op==2: + p=rng.choice(pins);od=rng.randrange(2);assert bus.write(bus.ctx,0x3ff44088+p*4,4,od*4)==0;live[p][2]=od + elif op==3: + raw=rng.getrandbits(32)&sum(mapping);assert bus.write(bus.ctx,0x3ff48474,4,raw)==0 + elif op==4: + mode=rng.randrange(3);unhold=mode==2;force=mode==1;armed=bool(rng.randrange(2));clear=bool(rng.randrange(2)) + word=0xaaaa1000|(0x4000 if unhold else 0)|(0x8000 if force else 0)|(0x800 if armed else 0)|(0x400 if clear else 0) + assert bus.write(bus.ctx,0x3ff48088,4,word)==0 + if clear:latched=False + elif op==5: + assert event(m)==0 + if armed:latched=True + else: + level=1-level;assert inputs(m,34,1,level)==0 + for p in pins: + holding=not unhold and (force or latched or bool(raw&mapping[p])) + if holding and p not in held:snap[p]=tuple(live[p]) + if holding:held.add(p) + else:held.discard(p) + data,enable,drain=snap[p] if holding else live[p] + wanted=-1 if not enable or (drain and data) else data + assert output(m,p,C.byref(pad))==0 and pad.drive==wanted,('hold referee',turn,p,wanted,pad.drive) + for address,wanted in [(0x3ff44004,out),(0x3ff48474,raw),(0x3ff44040,level<<2),(0x3ff48088,0xaaaa1000|(0x4000 if unhold else 0)|(0x8000 if force else 0)|(0x800 if armed else 0)|(0x200 if latched else 0))]: + assert bus.read(bus.ctx,address,4,C.byref(value))==0 and value.value==wanted,('register referee',turn,hex(address),hex(value.value),hex(wanted)) + return 4096 + + def main(): ap=argparse.ArgumentParser(description=__doc__) for n in ['gpio-reg','gpio-ll','gpio-struct','rtc-reg','misc','caps','sig-map']:ap.add_argument('--'+n,type=Path,required=True) for n in ['compiler','objcopy','linker','nm']:ap.add_argument('--'+n,required=True) + ap.add_argument('--per-pin-hold',action='store_true',help='also qualify digital per-pin hold and global overlap') + ap.add_argument('--rtc-io-reg',type=Path);ap.add_argument('--gpio-periph',type=Path) ap.add_argument('--cc',default='cc');args=ap.parse_args();rng=random.Random(0xaaaa5000) + if args.per_pin_hold and (args.rtc_io_reg is None or args.gpio_periph is None):ap.error('per-pin hold requires --rtc-io-reg and --gpio-periph') + io=read_header(args.rtc_io_reg,'a2936c43b3735fae816b7f4c4b76c76ce90694a50bba56d310e36379cf5f1119') if args.per_pin_hold else '' + periph=read_header(args.gpio_periph,'bef42aa91bbea259885a60a535a8fd89fa4137b4c2af3d8ba8ac9b547032329a') if args.per_pin_hold else '' reg=read_header(args.gpio_reg,'87609e6eb2349cfaa569344e3ee0fb6c38b9a1dbb23b81af32e701c53af1cf84') ll=read_header(args.gpio_ll,'e1c839b9fe34ce525af1c51935a8b0409aade38452516daadf2f3f62062a2907') struct=read_header(args.gpio_struct,'d7a6a542b8a0aa6e9530ca3a9201aefd4f12b195169cc8613a191bda2ed6cfc8') @@ -42,7 +89,8 @@ def main(): masks=[sum(1<<(n%32) for n in pins if n//32==b) for b in range(2)] def address(name):return 0x3ff44000+int(re.search(r'#define\s+GPIO_'+name+r'_REG\s+\(DR_REG_GPIO_BASE\s*\+\s*(0x[0-9a-f]+)\)',reg)[1],16) helpers=[] - for name in ['output_enable','output_disable','od_enable','od_disable','set_level','get_level','deep_sleep_hold_en','deep_sleep_hold_dis','deep_sleep_hold_is_en']: + names= ['output_enable','output_disable','od_enable','od_disable','set_level','get_level','deep_sleep_hold_en','deep_sleep_hold_dis','deep_sleep_hold_is_en']+(['hold_en','hold_dis'] if args.per_pin_hold else []) + for name in names: match=re.search(r'^static inline [^\n]*\bgpio_ll_'+name+r'\([^\n]*\)\n\{',ll,re.M);assert match,name end=match.end();depth=1 while depth: @@ -119,24 +167,41 @@ def bind(name,result,*params):f=getattr(api,name);f.restype=result;f.argtypes=li return input|(held<<1); } ''' + if args.per_pin_hold: + array=re.search(r'const uint32_t GPIO_HOLD_MASK\[SOC_GPIO_PIN_COUNT\] = \{.*?\};',periph,re.S)[0] + mapping=[(1< Date: Fri, 6 Nov 2026 00:00:18 +0000 Subject: [PATCH 41/57] Count only exercised SDK helpers in per-pin GPIO evidence --- CHANGELOG.md | 2 +- docs/STATUS.md | 2 +- docs/engineering/esp32-digital-pad-hold.md | 5 +++-- tests/vendor/README.md | 2 +- tests/vendor/xtensa_integer/run_gpio.py | 1 + 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 687797dc7..1094af9ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ versions follow semantic versioning. There is no release yet. controls, live resolved input samples, raw register changes and CPU-reset retention. Unsupported/package-specific bits fail without partial capture. - 3,455 new native assertions, 4,096 independent transition turns and 256 compiled - C calls with eleven unchanged SDK helpers pass. Native ROM now reaches 6,902 + C calls with eight unchanged SDK helpers pass. Native ROM now reaches 6,902 retirements, then IO_MUX U0TXD read; full pin routing/SDK boot remain unfinished. ### Original ESP32 TIMG0 RTC calibration counter diff --git a/docs/STATUS.md b/docs/STATUS.md index c99339aa4..a8394f400 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -10,7 +10,7 @@ does not mean the described behaviour exists. **Latest digital pad-hold update:** [Per-pin digital hold](engineering/esp32-digital-pad-hold.md) now follows the SDK bit mapping and combines local/global/auto-hold causes with FORCE_UNHOLD precedence and independent pad snapshots. 3,455 new native checks, -4,096 independent transition turns and 256 compiled C calls using eleven unchanged +4,096 independent transition turns and 256 compiled C calls using eight unchanged SDK helpers pass. Native ROM now reaches 6,902 retirements and IO_MUX U0TXD read at 0x3ff49088. RTC pad-specific hold, IO_MUX/peripheral routing, physical board behavior and complete SDK boot remain unqualified. diff --git a/docs/engineering/esp32-digital-pad-hold.md b/docs/engineering/esp32-digital-pad-hold.md index 3f60f9741..dad6cf89f 100644 --- a/docs/engineering/esp32-digital-pad-hold.md +++ b/docs/engineering/esp32-digital-pad-hold.md @@ -85,7 +85,7 @@ register and all captures; no supported bit is partially applied first. projected outputs plus raw register/status/input observations across mixed local/global/unhold/auto-hold events. - **256 actual ESP32 C interactive calls / 30,592 retired instructions** run on - both native CPUs using **eleven unchanged SDK helper bodies**. The C program + both native CPUs using **eight unchanged SDK helper bodies**. The C program uses the SDK's real hold-mask array, enables a pin hold, changes output controls, reads a live button/input, then releases the hold. Host events add/remove a global hold while the local hold remains set. Output and input-only operating @@ -100,7 +100,8 @@ Add the following to the [existing GPIO reproduction command](esp32-gpio-pad-hol --gpio-periph "$IDF_PATH/components/soc/esp32/gpio_periph.c" ``` -The default command keeps its original scope. In the extended mode the linker +The default command keeps its original scope. Its nine helpers and the extended +mode's eight helpers cover eleven distinct SDK helper bodies in total. In the extended mode the linker places the SDK mapping constant in the test image's read-only data section, and the fixture loads both `.text` and `.rodata` into the supplied IRAM image. The native CPU reads the real compiled lookup table; the host does not replace it. diff --git a/tests/vendor/README.md b/tests/vendor/README.md index 8f0c1e4ee..65cf3410c 100644 --- a/tests/vendor/README.md +++ b/tests/vendor/README.md @@ -267,7 +267,7 @@ clock-enable/delay/calibration functions or physical frequency/timing. [Counter/controller contract](../../docs/engineering/esp32-rtc-calibration.md). `xtensa_integer/run_gpio.py --per-pin-hold` adds 4,096 independent transition -turns and 256 compiled C calls using eleven unchanged SDK helpers and the actual +turns and 256 compiled C calls using eight unchanged SDK helpers and the actual hold-mask table. It checks local/global overlap, force-unhold, open-drain/enable capture and live inputs. RTC pad controls, peripheral routing and electrical behavior remain unqualified. [Digital per-pin contract](../../docs/engineering/esp32-digital-pad-hold.md). diff --git a/tests/vendor/xtensa_integer/run_gpio.py b/tests/vendor/xtensa_integer/run_gpio.py index 8470a59cf..5c82e07cd 100644 --- a/tests/vendor/xtensa_integer/run_gpio.py +++ b/tests/vendor/xtensa_integer/run_gpio.py @@ -90,6 +90,7 @@ def main(): def address(name):return 0x3ff44000+int(re.search(r'#define\s+GPIO_'+name+r'_REG\s+\(DR_REG_GPIO_BASE\s*\+\s*(0x[0-9a-f]+)\)',reg)[1],16) helpers=[] names= ['output_enable','output_disable','od_enable','od_disable','set_level','get_level','deep_sleep_hold_en','deep_sleep_hold_dis','deep_sleep_hold_is_en']+(['hold_en','hold_dis'] if args.per_pin_hold else []) + if args.per_pin_hold:names=[n for n in names if not n.startswith('deep_sleep_')] for name in names: match=re.search(r'^static inline [^\n]*\bgpio_ll_'+name+r'\([^\n]*\)\n\{',ll,re.M);assert match,name end=match.end();depth=1 From f80ddfcc5cd9af9125f4e3d663d5e1834b2fbedf Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Sun, 8 Nov 2026 03:18:34 +0000 Subject: [PATCH 42/57] ESP32: Xtensa LX6 cores in the machine framework, and the SoC that boots ESP-IDF Two LX6 cores (core/mcu/xtensa/lx6.c, lx6fp.c) as core-isa.h configures the ESP32: windowed registers, loops, XEA2 exceptions, seven interrupt levels, CCOUNT/CCOMPARE timers, MAC16, booleans, single-precision FPU, region protection, the debug option; backend "xtensa" in build.c. The ESP32 description (catalog/mcus/espressif/esp32): the memory map, the ROM image built from Espressif's esp32_rev300_rom.elf (equal to QEMU's esp32-v3-rom.bin), the register file from esp32.svd, and models for DPORT (APP_CPU control, caches and flash MMU, interrupt matrix, the APP_CPU's own ROM view), RTC_CNTL, eFuse, APB_CTRL clocks, GPIO/IO_MUX with strapping, UART0-2, TIMG0/1, SPI0/1 and the GT25Q32A flash, SHA, the internal analog I2C, RNG and the AHB view. Espressif's ROM, ESP-IDF v5.4's second-stage bootloader and hello_world run unmodified to Hello world! --- catalog/boards/esp32_devkitc/board.json | 25 + .../espressif/esp32/LICENSES/Apache-2.0.txt | 13 + catalog/mcus/espressif/esp32/mcu.json | 310 + catalog/mcus/espressif/esp32/registers.json | 51212 ++++++++++++++++ catalog/mcus/espressif/esp32/rom.bin | Bin 0 -> 458752 bytes catalog/mcus/espressif/esp32/wroom32e.json | 205 + core/mcu/build.c | 4 + core/mcu/esp32/soc/apbctrl.c | 98 + core/mcu/esp32/soc/dport.c | 346 + core/mcu/esp32/soc/efuse.c | 261 + core/mcu/esp32/soc/esp32.h | 105 + core/mcu/esp32/soc/gpio.c | 534 + core/mcu/esp32/soc/misc.c | 93 + core/mcu/esp32/soc/nor.c | 365 + core/mcu/esp32/soc/regi2c.c | 100 + core/mcu/esp32/soc/rtc.c | 398 + core/mcu/esp32/soc/sha.c | 202 + core/mcu/esp32/soc/spi.c | 354 + core/mcu/esp32/soc/timg.c | 434 + core/mcu/esp32/soc/uart.c | 452 + core/mcu/models.c | 11 + core/mcu/rf.c | 9 + core/mcu/rf.h | 2 + core/mcu/xtensa/lx6.c | 2244 + core/mcu/xtensa/lx6.h | 63 + core/mcu/xtensa/lx6fp.c | 418 + tools/importers/esprom.py | 4 +- 27 files changed, 58261 insertions(+), 1 deletion(-) create mode 100644 catalog/boards/esp32_devkitc/board.json create mode 100644 catalog/mcus/espressif/esp32/LICENSES/Apache-2.0.txt create mode 100644 catalog/mcus/espressif/esp32/mcu.json create mode 100644 catalog/mcus/espressif/esp32/registers.json create mode 100644 catalog/mcus/espressif/esp32/rom.bin create mode 100644 catalog/mcus/espressif/esp32/wroom32e.json create mode 100644 core/mcu/esp32/soc/apbctrl.c create mode 100644 core/mcu/esp32/soc/dport.c create mode 100644 core/mcu/esp32/soc/efuse.c create mode 100644 core/mcu/esp32/soc/esp32.h create mode 100644 core/mcu/esp32/soc/gpio.c create mode 100644 core/mcu/esp32/soc/misc.c create mode 100644 core/mcu/esp32/soc/nor.c create mode 100644 core/mcu/esp32/soc/regi2c.c create mode 100644 core/mcu/esp32/soc/rtc.c create mode 100644 core/mcu/esp32/soc/sha.c create mode 100644 core/mcu/esp32/soc/spi.c create mode 100644 core/mcu/esp32/soc/timg.c create mode 100644 core/mcu/esp32/soc/uart.c create mode 100644 core/mcu/xtensa/lx6.c create mode 100644 core/mcu/xtensa/lx6.h create mode 100644 core/mcu/xtensa/lx6fp.c diff --git a/catalog/boards/esp32_devkitc/board.json b/catalog/boards/esp32_devkitc/board.json new file mode 100644 index 000000000..2068109da --- /dev/null +++ b/catalog/boards/esp32_devkitc/board.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "kind": "board", + "id": "emblink/esp32_devkitc", + "version": "0.1.0", + "name": "ESP32-DevKitC V4", + "description": "Espressif's ESP32-DevKitC V4 with an ESP32-WROOM-32E module.", + "keywords": ["esp32", "devkitc", "esp32-wroom-32e", "xtensa", "espressif"], + "revision": "V4", + "mcu": {"ref": "espressif/esp32", "package": "wroom32e", "instance": "U1"}, + "oscillators": {"hse": {"hz": 40000000, "source": "external_crystal"}}, + "components": [], + "nets": { + "3V3": ["U1.VDD3P3"], + "GND": ["U1.GND"], + "EN": ["U1.CHIP_EN"], + "GPIO0": ["U1.GPIO0"], + "GPIO1": ["U1.GPIO1"], + "GPIO3": ["U1.GPIO3"] + }, + "ports": {}, + "console": {"peripheral": "UART0", "tx": "GPIO1", "rx": "GPIO3", "baud": 115200}, + "fidelity": {"functional": {"claims": "Work in progress.", "omissions": [], "validation": []}}, + "provenance": {"author": "EmbSIM", "license": "Apache-2.0", "sources": []} +} diff --git a/catalog/mcus/espressif/esp32/LICENSES/Apache-2.0.txt b/catalog/mcus/espressif/esp32/LICENSES/Apache-2.0.txt new file mode 100644 index 000000000..439f2cf82 --- /dev/null +++ b/catalog/mcus/espressif/esp32/LICENSES/Apache-2.0.txt @@ -0,0 +1,13 @@ +Copyright (c) 2026 Microchip Technology Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/catalog/mcus/espressif/esp32/mcu.json b/catalog/mcus/espressif/esp32/mcu.json new file mode 100644 index 000000000..62e1209d2 --- /dev/null +++ b/catalog/mcus/espressif/esp32/mcu.json @@ -0,0 +1,310 @@ +{ + "schema_version": 1, + "kind": "mcu", + "id": "espressif/esp32", + "version": "0.1.0", + "name": "ESP32", + "description": "Espressif ESP32 (ESP32-D0WD-V3, chip revision v3.1): two Xtensa LX6 cores at up to 240 MHz, 520 KiB of SRAM, 448 KiB of ROM, 16 KiB of RTC memory, external SPI flash behind the cores' caches, Wi-Fi and Bluetooth.", + "keywords": [ + "xtensa", + "lx6", + "espressif", + "esp32", + "esp32-d0wd-v3", + "dual-core" + ], + "vendor": "espressif", + "family": "esp32", + "core": { + "backend": "xtensa", + "model": "esp32-lx6", + "cores": 2, + "timing_tier": "estimated", + "fpu": true, + "mpu": false, + "endian": "little", + "reset_pc": "0x40000400" + }, + "memory": [ + { + "name": "rom", + "base": "0x40000000", + "size": "448K", + "kind": "rom", + "file": "rom.bin", + "doc": "Internal ROM0 (384 KiB) and ROM1 (64 KiB) on the instruction bus (TRM v5 table 3.3-1, ESP-IDF's SOC_IROM_MASK_HIGH 0x40070000): the boot ROM of chip revision v3, its image made by tools/importers/esprom.py (--base 0x40000000 --size 0x70000 --data-table 0x4000D4EC:0x4000D4F8, the Bluetooth controller's entry; --data-table 0x4000D4F8:0x4000D5C8, the ROM's copy table of its RAM variables' initial values) from Espressif's esp32_rev300_rom.elf (espressif/esp-rom-elfs 20241011, Apache-2.0: LICENSES/Apache-2.0.txt; the ROM's code is Copyright (c) 2015-2022 Espressif Systems (Shanghai) Co. Ltd.). It equals byte for byte the image Espressif's QEMU loads (esp32-v3-rom.bin, sha256 9115bbcd...258b) over its 455,722 bytes; the rest reads zeros" + }, + { + "name": "rom1_data", + "base": "0x3FF90000", + "size": "64K", + "kind": "alias", + "target": "0x40060000", + "doc": "Internal ROM1 on the data bus" + }, + { + "name": "sram0", + "base": "0x40070000", + "size": "192K", + "kind": "ram", + "doc": "Internal SRAM0 on the instruction bus: its first 64 KiB the cores' flash caches once they are enabled, the rest IRAM" + }, + { + "name": "sram2", + "base": "0x3FFAE000", + "size": "200K", + "kind": "ram", + "doc": "Internal SRAM2 on the data bus (DRAM)" + }, + { + "name": "sram1", + "base": "0x3FFE0000", + "size": "128K", + "kind": "ram", + "main": true, + "doc": "Internal SRAM1 on the data bus (DRAM), where the ROM keeps its data and stacks" + }, + { + "name": "rtc_fast", + "base": "0x3FF80000", + "size": "8K", + "kind": "ram", + "doc": "RTC FAST memory on the data bus, kept in deep sleep" + }, + { + "name": "rtc_fast_ibus", + "base": "0x400C0000", + "size": "8K", + "kind": "alias", + "target": "0x3FF80000", + "doc": "RTC FAST memory on the instruction bus" + }, + { + "name": "rtc_slow", + "base": "0x50000000", + "size": "8K", + "kind": "ram", + "doc": "RTC SLOW memory, kept in deep sleep, the ULP coprocessor's" + }, + { + "name": "flash", + "base": "0x0", + "size": "4M", + "kind": "external", + "erased": "0xFFFFFFFF", + "doc": "The module's 4 MB SPI flash, reached through SPI0 and SPI1 and read in place through the caches" + }, + { + "name": "flash_drom", + "base": "0x3F400000", + "size": "4M", + "kind": "window", + "doc": "The data bus's window on the flash (DROM0, TRM table 3.3-3): 64 pages of 64 KB that MMU entries 0-63 map" + }, + { + "name": "flash_irom", + "base": "0x400D0000", + "size": "0x330000", + "kind": "window", + "doc": "The instruction bus's window on the flash (IROM0 from the first whole page after RTC FAST memory to 0x403FFFFF): MMU entries 77-127" + } + ], + "clocks": { + "sources": [ + { + "name": "xtal", + "hz": 40000000, + "doc": "The module's 40 MHz crystal (XTL_CLK); the CPU runs from it out of reset" + }, + { + "name": "rc_fast", + "hz": 8000000, + "doc": "The internal 8 MHz RC oscillator (RC_FAST_CLK, 8MD256 its /256)" + }, + { + "name": "rc_slow", + "hz": 150000, + "doc": "The internal 150 kHz RC oscillator (RTC_SLOW_CLK's default)" + }, + { + "name": "pll", + "hz": 320000000, + "doc": "PLL_CLK, 320 MHz (or 480 MHz) from the crystal" + } + ], + "reset_core_hz": 40000000, + "max_core_hz": 240000000 + }, + "register_file": { + "format": "embsim", + "file": "registers.json", + "vendored": true, + "license": "Apache-2.0 (converted from Espressif's esp32.svd, Copyright 2024 Espressif Systems (Shanghai) PTE LTD; LICENSES/Apache-2.0.txt)" + }, + "peripherals": [ + { + "match": "DPORT", + "model": "esp32-dport", + "order": 0 + }, + { + "match": "RTC_CNTL", + "model": "esp32-rtc", + "order": 1 + }, + { + "match": "APB_CTRL", + "model": "esp32-apbctrl", + "order": 1 + }, + { + "match": "EFUSE", + "model": "esp32-efuse" + }, + { + "match": "UART?", + "model": "esp32-uart" + }, + { + "match": "IO_MUX", + "model": "esp32-iomux", + "order": 0 + }, + { + "match": "GPIO", + "model": "esp32-gpio", + "order": 1 + }, + { + "match": "SPI0", + "model": "esp32-spi" + }, + { + "match": "SPI1", + "model": "esp32-spi" + }, + { + "match": "TIMG?", + "model": "esp32-timg" + }, + { + "match": "SHA", + "model": "esp32-sha" + } + ], + "io": { + "family": "cmos_3v3", + "ports": [ + { + "name": "GPIO", + "pins": 40, + "mask": "0xFFFFFFFFFF" + } + ], + "electrical": { + "vdd": 3.3, + "voh": 2.64, + "vol": 0.33, + "rout": 20, + "vih": 2.475, + "vil": 0.825, + "hysteresis": 0.1, + "rpull": 45000, + "leak_a": 5e-08, + "ioh_max": 0.04, + "iol_max": 0.028 + } + }, + "console": { + "default": "UART0" + }, + "fidelity": { + "functional": { + "claims": "Work in progress.", + "omissions": [], + "validation": [] + }, + "timed": { + "claims": "Instruction timing estimated.", + "omissions": [], + "validation": [] + }, + "electrical": { + "claims": "GPIO pin-model defaults at 3.3 V.", + "omissions": [], + "validation": [] + } + }, + "provenance": { + "author": "EmbSIM", + "license": "Apache-2.0", + "sources": [ + { + "title": "ESP32 Technical Reference Manual", + "rev": "v5.2" + }, + { + "title": "ESP32 Series Datasheet", + "rev": "v4.8" + }, + { + "title": "Espressif esp32.svd (espressif/svd)", + "rev": "be20aa1" + }, + { + "title": "Espressif esp-rom-elfs, esp32_rev300_rom.elf", + "rev": "20241011" + } + ], + "notes": "The register description is converted from Espressif's Apache-2.0 SVD; the boot ROM's image from Espressif's Apache-2.0 ROM ELF." + }, + "signals": [ + "GPIO0", + "GPIO1", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "GPIO8", + "GPIO9", + "GPIO10", + "GPIO11", + "GPIO12", + "GPIO13", + "GPIO14", + "GPIO15", + "GPIO16", + "GPIO17", + "GPIO18", + "GPIO19", + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO25", + "GPIO26", + "GPIO27", + "GPIO32", + "GPIO33", + "GPIO34", + "GPIO35", + "GPIO36", + "GPIO37", + "GPIO38", + "GPIO39", + "CHIP_EN", + "GND", + "LNA_IN", + "VDD3P3", + "VDD3P3_CPU", + "VDD3P3_RTC", + "VDDA", + "VDD_SDIO", + "XTAL_N", + "XTAL_P" + ], + "packages": { + "wroom32e": "wroom32e.json" + } +} \ No newline at end of file diff --git a/catalog/mcus/espressif/esp32/registers.json b/catalog/mcus/espressif/esp32/registers.json new file mode 100644 index 000000000..7287b6ae6 --- /dev/null +++ b/catalog/mcus/espressif/esp32/registers.json @@ -0,0 +1,51212 @@ +{ + "schema_version": 1, + "kind": "registers", + "id": "espressif/esp32/registers", + "version": "1.0.0", + "device": "ESP32", + "source": "converted from esp32.svd by tools/importers/svd2regs.py; peripherals left out: RNG (the random number generator's one register (RNG_DATA, TRM 26), which the SVD puts at the AHB address 0x60035144; the chip's model answers at 0x3FF75144 and its AHB alias (misc.c))", + "interrupts": { + "WIFI_MAC": 0, + "WIFI_NMI": 1, + "WIFI_BB": 2, + "BT_MAC": 3, + "BT_BB": 4, + "BT_BB_NMI": 5, + "RWBT": 6, + "RWBLE": 7, + "RWBT_NMI": 8, + "RWBLE_NMI": 9, + "UHCI0": 12, + "UHCI1": 13, + "TG0_T0_LEVEL": 14, + "TG0_T1_LEVEL": 15, + "TG0_WDT_LEVEL": 16, + "TG0_LACT_LEVEL": 17, + "TG1_T0_LEVEL": 18, + "TG1_T1_LEVEL": 19, + "TG1_WDT_LEVEL": 20, + "TG1_LACT_LEVEL": 21, + "GPIO": 22, + "GPIO_NMI": 23, + "FROM_CPU_INTR0": 24, + "FROM_CPU_INTR1": 25, + "FROM_CPU_INTR2": 26, + "FROM_CPU_INTR3": 27, + "SPI0": 28, + "SPI1": 29, + "SPI2": 30, + "SPI3": 31, + "I2S0": 32, + "I2S1": 33, + "UART0": 34, + "UART1": 35, + "UART2": 36, + "SDIO_HOST": 37, + "ETH_MAC": 38, + "MCPWM0": 39, + "MCPWM1": 40, + "MCPWM2": 41, + "MCPWM3": 42, + "LEDC": 43, + "EFUSE": 44, + "TWAI0": 45, + "RTC_CORE": 46, + "RMT": 47, + "PCNT": 48, + "I2C_EXT0": 49, + "I2C_EXT1": 50, + "RSA": 51, + "SPI1_DMA": 52, + "SPI2_DMA": 53, + "SPI3_DMA": 54, + "WDT": 55, + "TIMER1": 56, + "TIMER2": 57, + "TG0_T0_EDGE": 58, + "TG0_T1_EDGE": 59, + "TG0_WDT_EDGE": 60, + "TG0_LACT_EDGE": 61, + "TG1_T0_EDGE": 62, + "TG1_T1_EDGE": 63, + "TG1_WDT_EDGE": 64, + "TG1_LACT_EDGE": 65, + "MMU_IA": 66, + "MPU_IA": 67, + "CACHE_IA": 68 + }, + "peripherals": [ + { + "name": "DPORT", + "base": "0x3FF00000", + "interrupts": [ + "WIFI_MAC", + "WIFI_NMI", + "WIFI_BB", + "BT_MAC", + "BT_BB", + "BT_BB_NMI", + "RWBT", + "RWBLE", + "RWBT_NMI", + "RWBLE_NMI", + "FROM_CPU_INTR0", + "FROM_CPU_INTR1", + "FROM_CPU_INTR2", + "FROM_CPU_INTR3", + "SDIO_HOST", + "ETH_MAC", + "WDT", + "MMU_IA", + "MPU_IA", + "CACHE_IA" + ], + "registers": [ + { + "name": "PRO_BOOT_REMAP_CTRL", + "offset": "0x00", + "fields": [ + [ + "PRO_BOOT_REMAP", + 0, + 1 + ] + ] + }, + { + "name": "APP_BOOT_REMAP_CTRL", + "offset": "0x04", + "fields": [ + [ + "APP_BOOT_REMAP", + 0, + 1 + ] + ] + }, + { + "name": "ACCESS_CHECK", + "offset": "0x08", + "fields": [ + [ + "PRO", + 0, + 1, + "r" + ], + [ + "APP", + 8, + 1, + "r" + ] + ] + }, + { + "name": "PRO_DPORT_APB_MASK0", + "offset": "0x0C", + "fields": [ + [ + "PRODPORT_APB_MASK0", + 0, + 32 + ] + ] + }, + { + "name": "PRO_DPORT_APB_MASK1", + "offset": "0x10", + "fields": [ + [ + "PRODPORT_APB_MASK1", + 0, + 32 + ] + ] + }, + { + "name": "APP_DPORT_APB_MASK0", + "offset": "0x14", + "fields": [ + [ + "APPDPORT_APB_MASK0", + 0, + 32 + ] + ] + }, + { + "name": "APP_DPORT_APB_MASK1", + "offset": "0x18", + "fields": [ + [ + "APPDPORT_APB_MASK1", + 0, + 32 + ] + ] + }, + { + "name": "PERI_CLK_EN", + "offset": "0x1C", + "fields": [ + [ + "PERI_CLK_EN", + 0, + 32 + ] + ] + }, + { + "name": "PERI_RST_EN", + "offset": "0x20", + "fields": [ + [ + "PERI_RST_EN", + 0, + 32 + ] + ] + }, + { + "name": "WIFI_BB_CFG", + "offset": "0x24", + "fields": [ + [ + "WIFI_BB_CFG", + 0, + 32 + ] + ] + }, + { + "name": "WIFI_BB_CFG_2", + "offset": "0x28", + "fields": [ + [ + "WIFI_BB_CFG_2", + 0, + 32 + ] + ] + }, + { + "name": "APPCPU_CTRL_A", + "offset": "0x2C", + "reset": "0x00000001", + "fields": [ + [ + "APPCPU_RESETTING", + 0, + 1 + ] + ] + }, + { + "name": "APPCPU_CTRL_B", + "offset": "0x30", + "fields": [ + [ + "APPCPU_CLKGATE_EN", + 0, + 1 + ] + ] + }, + { + "name": "APPCPU_CTRL_C", + "offset": "0x34", + "fields": [ + [ + "APPCPU_RUNSTALL", + 0, + 1 + ] + ] + }, + { + "name": "APPCPU_CTRL_D", + "offset": "0x38", + "fields": [ + [ + "APPCPU_BOOT_ADDR", + 0, + 32 + ] + ] + }, + { + "name": "CPU_PER_CONF", + "offset": "0x3C", + "fields": [ + [ + "CPUPERIOD_SEL", + 0, + 2 + ], + [ + "LOWSPEED_CLK_SEL", + 2, + 1 + ], + [ + "FAST_CLK_RTC_SEL", + 3, + 1 + ] + ] + }, + { + "name": "PRO_CACHE_CTRL", + "offset": "0x40", + "reset": "0x00000010", + "fields": [ + [ + "PRO_CACHE_MODE", + 2, + 1 + ], + [ + "PRO_CACHE_ENABLE", + 3, + 1 + ], + [ + "PRO_CACHE_FLUSH_ENA", + 4, + 1 + ], + [ + "PRO_CACHE_FLUSH_DONE", + 5, + 1, + "r" + ], + [ + "PRO_CACHE_LOCK_0_EN", + 6, + 1 + ], + [ + "PRO_CACHE_LOCK_1_EN", + 7, + 1 + ], + [ + "PRO_CACHE_LOCK_2_EN", + 8, + 1 + ], + [ + "PRO_CACHE_LOCK_3_EN", + 9, + 1 + ], + [ + "PRO_SINGLE_IRAM_ENA", + 10, + 1 + ], + [ + "PRO_DRAM_SPLIT", + 11, + 1 + ], + [ + "PRO_AHB_SPI_REQ", + 12, + 1, + "r" + ], + [ + "PRO_SLAVE_REQ", + 13, + 1, + "r" + ], + [ + "AHB_SPI_REQ", + 14, + 1, + "r" + ], + [ + "SLAVE_REQ", + 15, + 1, + "r" + ], + [ + "PRO_DRAM_HL", + 16, + 1 + ] + ] + }, + { + "name": "PRO_CACHE_CTRL1", + "offset": "0x44", + "reset": "0x000008FF", + "fields": [ + [ + "PRO_CACHE_MASK_IRAM0", + 0, + 1 + ], + [ + "PRO_CACHE_MASK_IRAM1", + 1, + 1 + ], + [ + "PRO_CACHE_MASK_IROM0", + 2, + 1 + ], + [ + "PRO_CACHE_MASK_DRAM1", + 3, + 1 + ], + [ + "PRO_CACHE_MASK_DROM0", + 4, + 1 + ], + [ + "PRO_CACHE_MASK_OPSDRAM", + 5, + 1 + ], + [ + "PRO_CMMU_SRAM_PAGE_MODE", + 6, + 3 + ], + [ + "PRO_CMMU_FLASH_PAGE_MODE", + 9, + 2 + ], + [ + "PRO_CMMU_FORCE_ON", + 11, + 1 + ], + [ + "PRO_CMMU_PD", + 12, + 1 + ], + [ + "PRO_CACHE_MMU_IA_CLR", + 13, + 1 + ] + ] + }, + { + "name": "PRO_CACHE_LOCK_0_ADDR", + "offset": "0x48", + "fields": [ + [ + "PRE", + 0, + 14 + ], + [ + "MIN", + 14, + 4 + ], + [ + "MAX", + 18, + 4 + ] + ] + }, + { + "name": "PRO_CACHE_LOCK_1_ADDR", + "offset": "0x4C", + "fields": [ + [ + "PRE", + 0, + 14 + ], + [ + "MIN", + 14, + 4 + ], + [ + "MAX", + 18, + 4 + ] + ] + }, + { + "name": "PRO_CACHE_LOCK_2_ADDR", + "offset": "0x50", + "fields": [ + [ + "PRE", + 0, + 14 + ], + [ + "MIN", + 14, + 4 + ], + [ + "MAX", + 18, + 4 + ] + ] + }, + { + "name": "PRO_CACHE_LOCK_3_ADDR", + "offset": "0x54", + "fields": [ + [ + "PRE", + 0, + 14 + ], + [ + "MIN", + 14, + 4 + ], + [ + "MAX", + 18, + 4 + ] + ] + }, + { + "name": "APP_CACHE_CTRL", + "offset": "0x58", + "reset": "0x00000010", + "fields": [ + [ + "APP_CACHE_MODE", + 2, + 1 + ], + [ + "APP_CACHE_ENABLE", + 3, + 1 + ], + [ + "APP_CACHE_FLUSH_ENA", + 4, + 1 + ], + [ + "APP_CACHE_FLUSH_DONE", + 5, + 1, + "r" + ], + [ + "APP_CACHE_LOCK_0_EN", + 6, + 1 + ], + [ + "APP_CACHE_LOCK_1_EN", + 7, + 1 + ], + [ + "APP_CACHE_LOCK_2_EN", + 8, + 1 + ], + [ + "APP_CACHE_LOCK_3_EN", + 9, + 1 + ], + [ + "APP_SINGLE_IRAM_ENA", + 10, + 1 + ], + [ + "APP_DRAM_SPLIT", + 11, + 1 + ], + [ + "APP_AHB_SPI_REQ", + 12, + 1, + "r" + ], + [ + "APP_SLAVE_REQ", + 13, + 1, + "r" + ], + [ + "APP_DRAM_HL", + 14, + 1 + ] + ] + }, + { + "name": "APP_CACHE_CTRL1", + "offset": "0x5C", + "reset": "0x000008FF", + "fields": [ + [ + "APP_CACHE_MASK_IRAM0", + 0, + 1 + ], + [ + "APP_CACHE_MASK_IRAM1", + 1, + 1 + ], + [ + "APP_CACHE_MASK_IROM0", + 2, + 1 + ], + [ + "APP_CACHE_MASK_DRAM1", + 3, + 1 + ], + [ + "APP_CACHE_MASK_DROM0", + 4, + 1 + ], + [ + "APP_CACHE_MASK_OPSDRAM", + 5, + 1 + ], + [ + "APP_CMMU_SRAM_PAGE_MODE", + 6, + 3 + ], + [ + "APP_CMMU_FLASH_PAGE_MODE", + 9, + 2 + ], + [ + "APP_CMMU_FORCE_ON", + 11, + 1 + ], + [ + "APP_CMMU_PD", + 12, + 1 + ], + [ + "APP_CACHE_MMU_IA_CLR", + 13, + 1 + ] + ] + }, + { + "name": "APP_CACHE_LOCK_0_ADDR", + "offset": "0x60", + "fields": [ + [ + "PRE", + 0, + 14 + ], + [ + "MIN", + 14, + 4 + ], + [ + "MAX", + 18, + 4 + ] + ] + }, + { + "name": "APP_CACHE_LOCK_1_ADDR", + "offset": "0x64", + "fields": [ + [ + "PRE", + 0, + 14 + ], + [ + "MIN", + 14, + 4 + ], + [ + "MAX", + 18, + 4 + ] + ] + }, + { + "name": "APP_CACHE_LOCK_2_ADDR", + "offset": "0x68", + "fields": [ + [ + "PRE", + 0, + 14 + ], + [ + "MIN", + 14, + 4 + ], + [ + "MAX", + 18, + 4 + ] + ] + }, + { + "name": "APP_CACHE_LOCK_3_ADDR", + "offset": "0x6C", + "fields": [ + [ + "PRE", + 0, + 14 + ], + [ + "MIN", + 14, + 4 + ], + [ + "MAX", + 18, + 4 + ] + ] + }, + { + "name": "TRACEMEM_MUX_MODE", + "offset": "0x70", + "fields": [ + [ + "TRACEMEM_MUX_MODE", + 0, + 2 + ] + ] + }, + { + "name": "PRO_TRACEMEM_ENA", + "offset": "0x74", + "fields": [ + [ + "PRO_TRACEMEM_ENA", + 0, + 1 + ] + ] + }, + { + "name": "APP_TRACEMEM_ENA", + "offset": "0x78", + "fields": [ + [ + "APP_TRACEMEM_ENA", + 0, + 1 + ] + ] + }, + { + "name": "CACHE_MUX_MODE", + "offset": "0x7C", + "fields": [ + [ + "CACHE_MUX_MODE", + 0, + 2 + ] + ] + }, + { + "name": "IMMU_PAGE_MODE", + "offset": "0x80", + "fields": [ + [ + "INTERNAL_SRAM_IMMU_ENA", + 0, + 1 + ], + [ + "IMMU_PAGE_MODE", + 1, + 2 + ] + ] + }, + { + "name": "DMMU_PAGE_MODE", + "offset": "0x84", + "fields": [ + [ + "INTERNAL_SRAM_DMMU_ENA", + 0, + 1 + ], + [ + "DMMU_PAGE_MODE", + 1, + 2 + ] + ] + }, + { + "name": "ROM_MPU_ENA", + "offset": "0x88", + "fields": [ + [ + "SHARE_ROM_MPU_ENA", + 0, + 1 + ], + [ + "PRO_ROM_MPU_ENA", + 1, + 1 + ], + [ + "APP_ROM_MPU_ENA", + 2, + 1 + ] + ] + }, + { + "name": "MEM_PD_MASK", + "offset": "0x8C", + "reset": "0x00000001", + "fields": [ + [ + "LSLP_MEM_PD_MASK", + 0, + 1 + ] + ] + }, + { + "name": "ROM_PD_CTRL", + "offset": "0x90", + "fields": [ + [ + "PRO_ROM_PD", + 0, + 1 + ], + [ + "APP_ROM_PD", + 1, + 1 + ], + [ + "SHARE_ROM_PD", + 2, + 6 + ] + ] + }, + { + "name": "ROM_FO_CTRL", + "offset": "0x94", + "reset": "0x00000003", + "fields": [ + [ + "PRO_ROM_FO", + 0, + 1 + ], + [ + "APP_ROM_FO", + 1, + 1 + ], + [ + "SHARE_ROM_FO", + 2, + 6 + ] + ] + }, + { + "name": "SRAM_PD_CTRL_0", + "offset": "0x98", + "fields": [ + [ + "SRAM_PD_0", + 0, + 32 + ] + ] + }, + { + "name": "SRAM_PD_CTRL_1", + "offset": "0x9C", + "fields": [ + [ + "SRAM_PD_1", + 0, + 1 + ] + ] + }, + { + "name": "SRAM_FO_CTRL_0", + "offset": "0xA0", + "reset": "0xFFFFFFFF", + "fields": [ + [ + "SRAM_FO_0", + 0, + 32 + ] + ] + }, + { + "name": "SRAM_FO_CTRL_1", + "offset": "0xA4", + "reset": "0x00000001", + "fields": [ + [ + "SRAM_FO_1", + 0, + 1 + ] + ] + }, + { + "name": "IRAM_DRAM_AHB_SEL", + "offset": "0xA8", + "fields": [ + [ + "MASK_PRO_IRAM", + 0, + 1 + ], + [ + "MASK_APP_IRAM", + 1, + 1 + ], + [ + "MASK_PRO_DRAM", + 2, + 1 + ], + [ + "MASK_APP_DRAM", + 3, + 1 + ], + [ + "MASK_AHB", + 4, + 1 + ], + [ + "MAC_DUMP_MODE", + 5, + 2 + ] + ] + }, + { + "name": "TAG_FO_CTRL", + "offset": "0xAC", + "reset": "0x00000101", + "fields": [ + [ + "PRO_CACHE_TAG_FORCE_ON", + 0, + 1 + ], + [ + "PRO_CACHE_TAG_PD", + 1, + 1 + ], + [ + "APP_CACHE_TAG_FORCE_ON", + 8, + 1 + ], + [ + "APP_CACHE_TAG_PD", + 9, + 1 + ] + ] + }, + { + "name": "AHB_LITE_MASK", + "offset": "0xB0", + "fields": [ + [ + "PRO", + 0, + 1 + ], + [ + "APP", + 4, + 1 + ], + [ + "SDIO", + 8, + 1 + ], + [ + "PRODPORT", + 9, + 1 + ], + [ + "APPDPORT", + 10, + 1 + ], + [ + "AHB_LITE_SDHOST_PID", + 11, + 3 + ] + ] + }, + { + "name": "AHB_MPU_TABLE_0", + "offset": "0xB4", + "reset": "0xFFFFFFFF", + "fields": [ + [ + "AHB_ACCESS_GRANT_0", + 0, + 32 + ] + ] + }, + { + "name": "AHB_MPU_TABLE_1", + "offset": "0xB8", + "reset": "0x000001FF", + "fields": [ + [ + "AHB_ACCESS_GRANT_1", + 0, + 9 + ] + ] + }, + { + "name": "HOST_INF_SEL", + "offset": "0xBC", + "fields": [ + [ + "PERI_IO_SWAP", + 0, + 8 + ], + [ + "LINK_DEVICE_SEL", + 8, + 8 + ] + ] + }, + { + "name": "PERIP_CLK_EN", + "offset": "0xC0", + "reset": "0xF9C1E06F", + "fields": [ + [ + "TIMERS_CLK_EN", + 0, + 1 + ], + [ + "SPI01_CLK_EN", + 1, + 1 + ], + [ + "UART_CLK_EN", + 2, + 1 + ], + [ + "WDG_CLK_EN", + 3, + 1 + ], + [ + "I2S0_CLK_EN", + 4, + 1 + ], + [ + "UART1_CLK_EN", + 5, + 1 + ], + [ + "SPI2_CLK_EN", + 6, + 1 + ], + [ + "I2C0_EXT0_CLK_EN", + 7, + 1 + ], + [ + "UHCI0_CLK_EN", + 8, + 1 + ], + [ + "RMT_CLK_EN", + 9, + 1 + ], + [ + "PCNT_CLK_EN", + 10, + 1 + ], + [ + "LEDC_CLK_EN", + 11, + 1 + ], + [ + "UHCI1_CLK_EN", + 12, + 1 + ], + [ + "TIMERGROUP_CLK_EN", + 13, + 1 + ], + [ + "EFUSE_CLK_EN", + 14, + 1 + ], + [ + "TIMERGROUP1_CLK_EN", + 15, + 1 + ], + [ + "SPI3_CLK_EN", + 16, + 1 + ], + [ + "PWM0_CLK_EN", + 17, + 1 + ], + [ + "I2C_EXT1_CLK_EN", + 18, + 1 + ], + [ + "TWAI_CLK_EN", + 19, + 1 + ], + [ + "PWM1_CLK_EN", + 20, + 1 + ], + [ + "I2S1_CLK_EN", + 21, + 1 + ], + [ + "SPI_DMA_CLK_EN", + 22, + 1 + ], + [ + "UART2_CLK_EN", + 23, + 1 + ], + [ + "UART_MEM_CLK_EN", + 24, + 1 + ], + [ + "PWM2_CLK_EN", + 25, + 1 + ], + [ + "PWM3_CLK_EN", + 26, + 1 + ] + ] + }, + { + "name": "PERIP_RST_EN", + "offset": "0xC4", + "fields": [ + [ + "TIMERS_RST", + 0, + 1 + ], + [ + "SPI01_RST", + 1, + 1 + ], + [ + "UART_RST", + 2, + 1 + ], + [ + "WDG_RST", + 3, + 1 + ], + [ + "I2S0_RST", + 4, + 1 + ], + [ + "UART1_RST", + 5, + 1 + ], + [ + "SPI2_RST", + 6, + 1 + ], + [ + "I2C0_EXT0_RST", + 7, + 1 + ], + [ + "UHCI0_RST", + 8, + 1 + ], + [ + "RMT_RST", + 9, + 1 + ], + [ + "PCNT_RST", + 10, + 1 + ], + [ + "LEDC_RST", + 11, + 1 + ], + [ + "UHCI1_RST", + 12, + 1 + ], + [ + "TIMERGROUP_RST", + 13, + 1 + ], + [ + "EFUSE_RST", + 14, + 1 + ], + [ + "TIMERGROUP1_RST", + 15, + 1 + ], + [ + "SPI3_RST", + 16, + 1 + ], + [ + "PWM0_RST", + 17, + 1 + ], + [ + "I2C_EXT1_RST", + 18, + 1 + ], + [ + "TWAI_RST", + 19, + 1 + ], + [ + "PWM1_RST", + 20, + 1 + ], + [ + "I2S1_RST", + 21, + 1 + ], + [ + "SPI_DMA_RST", + 22, + 1 + ], + [ + "UART2_RST", + 23, + 1 + ], + [ + "UART_MEM_RST", + 24, + 1 + ], + [ + "PWM2_RST", + 25, + 1 + ], + [ + "PWM3_RST", + 26, + 1 + ] + ] + }, + { + "name": "SLAVE_SPI_CONFIG", + "offset": "0xC8", + "fields": [ + [ + "SLAVE_SPI_MASK_PRO", + 0, + 1 + ], + [ + "SLAVE_SPI_MASK_APP", + 4, + 1 + ], + [ + "SPI_ENCRYPT_ENABLE", + 8, + 1 + ], + [ + "SPI_DECRYPT_ENABLE", + 12, + 1 + ] + ] + }, + { + "name": "WIFI_CLK_EN", + "offset": "0xCC", + "reset": "0xFFFCE030", + "fields": [ + [ + "WIFI_CLK_EN", + 0, + 32 + ], + [ + "WIFI_CLK_WIFI_EN", + 0, + 3 + ], + [ + "WIFI_CLK_WIFI_BT_COMMON", + 0, + 6 + ], + [ + "WIFI_CLK_BT_EN", + 11, + 3 + ] + ] + }, + { + "name": "CORE_RST_EN", + "offset": "0xD0", + "fields": [ + [ + "CORE_RST", + 0, + 8 + ] + ] + }, + { + "name": "BT_LPCK_DIV_INT", + "offset": "0xD4", + "reset": "0x000000FF", + "fields": [ + [ + "BT_LPCK_DIV_NUM", + 0, + 12 + ], + [ + "BTEXTWAKEUP_REQ", + 12, + 1 + ] + ] + }, + { + "name": "BT_LPCK_DIV_FRAC", + "offset": "0xD8", + "reset": "0x02001001", + "fields": [ + [ + "BT_LPCK_DIV_B", + 0, + 12 + ], + [ + "BT_LPCK_DIV_A", + 12, + 12 + ], + [ + "LPCLK_SEL_RTC_SLOW", + 24, + 1 + ], + [ + "LPCLK_SEL_8M", + 25, + 1 + ], + [ + "LPCLK_SEL_XTAL", + 26, + 1 + ], + [ + "LPCLK_SEL_XTAL32K", + 27, + 1 + ] + ] + }, + { + "name": "CPU_INTR_FROM_CPU_0", + "offset": "0xDC", + "fields": [ + [ + "CPU_INTR_FROM_CPU_0", + 0, + 1 + ] + ] + }, + { + "name": "CPU_INTR_FROM_CPU_1", + "offset": "0xE0", + "fields": [ + [ + "CPU_INTR_FROM_CPU_1", + 0, + 1 + ] + ] + }, + { + "name": "CPU_INTR_FROM_CPU_2", + "offset": "0xE4", + "fields": [ + [ + "CPU_INTR_FROM_CPU_2", + 0, + 1 + ] + ] + }, + { + "name": "CPU_INTR_FROM_CPU_3", + "offset": "0xE8", + "fields": [ + [ + "CPU_INTR_FROM_CPU_3", + 0, + 1 + ] + ] + }, + { + "name": "PRO_INTR_STATUS_0", + "offset": "0xEC", + "fields": [ + [ + "PRO_INTR_STATUS_0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "PRO_INTR_STATUS_1", + "offset": "0xF0", + "fields": [ + [ + "PRO_INTR_STATUS_1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "PRO_INTR_STATUS_2", + "offset": "0xF4", + "fields": [ + [ + "PRO_INTR_STATUS_2", + 0, + 32, + "r" + ] + ] + }, + { + "name": "APP_INTR_STATUS_0", + "offset": "0xF8", + "fields": [ + [ + "APP_INTR_STATUS_0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "APP_INTR_STATUS_1", + "offset": "0xFC", + "fields": [ + [ + "APP_INTR_STATUS_1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "APP_INTR_STATUS_2", + "offset": "0x100", + "fields": [ + [ + "APP_INTR_STATUS_2", + 0, + 32, + "r" + ] + ] + }, + { + "name": "PRO_MAC_INTR_MAP", + "offset": "0x104", + "reset": "0x00000010", + "fields": [ + [ + "PRO_MAC_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_MAC_NMI_MAP", + "offset": "0x108", + "reset": "0x00000010", + "fields": [ + [ + "PRO_MAC_NMI_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_BB_INT_MAP", + "offset": "0x10C", + "reset": "0x00000010", + "fields": [ + [ + "PRO_BB_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_BT_MAC_INT_MAP", + "offset": "0x110", + "reset": "0x00000010", + "fields": [ + [ + "PRO_BT_MAC_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_BT_BB_INT_MAP", + "offset": "0x114", + "reset": "0x00000010", + "fields": [ + [ + "PRO_BT_BB_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_BT_BB_NMI_MAP", + "offset": "0x118", + "reset": "0x00000010", + "fields": [ + [ + "PRO_BT_BB_NMI_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_RWBT_IRQ_MAP", + "offset": "0x11C", + "reset": "0x00000010", + "fields": [ + [ + "PRO_RWBT_IRQ_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_RWBLE_IRQ_MAP", + "offset": "0x120", + "reset": "0x00000010", + "fields": [ + [ + "PRO_RWBLE_IRQ_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_RWBT_NMI_MAP", + "offset": "0x124", + "reset": "0x00000010", + "fields": [ + [ + "PRO_RWBT_NMI_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_RWBLE_NMI_MAP", + "offset": "0x128", + "reset": "0x00000010", + "fields": [ + [ + "PRO_RWBLE_NMI_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_SLC0_INTR_MAP", + "offset": "0x12C", + "reset": "0x00000010", + "fields": [ + [ + "PRO_SLC0_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_SLC1_INTR_MAP", + "offset": "0x130", + "reset": "0x00000010", + "fields": [ + [ + "PRO_SLC1_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_UHCI0_INTR_MAP", + "offset": "0x134", + "reset": "0x00000010", + "fields": [ + [ + "PRO_UHCI0_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_UHCI1_INTR_MAP", + "offset": "0x138", + "reset": "0x00000010", + "fields": [ + [ + "PRO_UHCI1_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TG_T0_LEVEL_INT_MAP", + "offset": "0x13C", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TG_T0_LEVEL_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TG_T1_LEVEL_INT_MAP", + "offset": "0x140", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TG_T1_LEVEL_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TG_WDT_LEVEL_INT_MAP", + "offset": "0x144", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TG_WDT_LEVEL_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TG_LACT_LEVEL_INT_MAP", + "offset": "0x148", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TG_LACT_LEVEL_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TG1_T0_LEVEL_INT_MAP", + "offset": "0x14C", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TG1_T0_LEVEL_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TG1_T1_LEVEL_INT_MAP", + "offset": "0x150", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TG1_T1_LEVEL_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TG1_WDT_LEVEL_INT_MAP", + "offset": "0x154", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TG1_WDT_LEVEL_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TG1_LACT_LEVEL_INT_MAP", + "offset": "0x158", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TG1_LACT_LEVEL_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_GPIO_INTERRUPT_MAP", + "offset": "0x15C", + "reset": "0x00000010", + "fields": [ + [ + "PRO_GPIO_INTERRUPT_PRO_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_GPIO_INTERRUPT_NMI_MAP", + "offset": "0x160", + "reset": "0x00000010", + "fields": [ + [ + "PRO_GPIO_INTERRUPT_PRO_NMI_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_CPU_INTR_FROM_CPU_0_MAP", + "offset": "0x164", + "reset": "0x00000010", + "fields": [ + [ + "PRO_CPU_INTR_FROM_CPU_0_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_CPU_INTR_FROM_CPU_1_MAP", + "offset": "0x168", + "reset": "0x00000010", + "fields": [ + [ + "PRO_CPU_INTR_FROM_CPU_1_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_CPU_INTR_FROM_CPU_2_MAP", + "offset": "0x16C", + "reset": "0x00000010", + "fields": [ + [ + "PRO_CPU_INTR_FROM_CPU_2_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_CPU_INTR_FROM_CPU_3_MAP", + "offset": "0x170", + "reset": "0x00000010", + "fields": [ + [ + "PRO_CPU_INTR_FROM_CPU_3_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_SPI_INTR_0_MAP", + "offset": "0x174", + "reset": "0x00000010", + "fields": [ + [ + "PRO_SPI_INTR_0_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_SPI_INTR_1_MAP", + "offset": "0x178", + "reset": "0x00000010", + "fields": [ + [ + "PRO_SPI_INTR_1_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_SPI_INTR_2_MAP", + "offset": "0x17C", + "reset": "0x00000010", + "fields": [ + [ + "PRO_SPI_INTR_2_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_SPI_INTR_3_MAP", + "offset": "0x180", + "reset": "0x00000010", + "fields": [ + [ + "PRO_SPI_INTR_3_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_I2S0_INT_MAP", + "offset": "0x184", + "reset": "0x00000010", + "fields": [ + [ + "PRO_I2S0_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_I2S1_INT_MAP", + "offset": "0x188", + "reset": "0x00000010", + "fields": [ + [ + "PRO_I2S1_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_UART_INTR_MAP", + "offset": "0x18C", + "reset": "0x00000010", + "fields": [ + [ + "PRO_UART_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_UART1_INTR_MAP", + "offset": "0x190", + "reset": "0x00000010", + "fields": [ + [ + "PRO_UART1_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_UART2_INTR_MAP", + "offset": "0x194", + "reset": "0x00000010", + "fields": [ + [ + "PRO_UART2_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_SDIO_HOST_INTERRUPT_MAP", + "offset": "0x198", + "reset": "0x00000010", + "fields": [ + [ + "PRO_SDIO_HOST_INTERRUPT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_EMAC_INT_MAP", + "offset": "0x19C", + "reset": "0x00000010", + "fields": [ + [ + "PRO_EMAC_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_PWM0_INTR_MAP", + "offset": "0x1A0", + "reset": "0x00000010", + "fields": [ + [ + "PRO_PWM0_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_PWM1_INTR_MAP", + "offset": "0x1A4", + "reset": "0x00000010", + "fields": [ + [ + "PRO_PWM1_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_PWM2_INTR_MAP", + "offset": "0x1A8", + "reset": "0x00000010", + "fields": [ + [ + "PRO_PWM2_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_PWM3_INTR_MAP", + "offset": "0x1AC", + "reset": "0x00000010", + "fields": [ + [ + "PRO_PWM3_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_LEDC_INT_MAP", + "offset": "0x1B0", + "reset": "0x00000010", + "fields": [ + [ + "PRO_LEDC_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_EFUSE_INT_MAP", + "offset": "0x1B4", + "reset": "0x00000010", + "fields": [ + [ + "PRO_EFUSE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_CAN_INT_MAP", + "offset": "0x1B8", + "reset": "0x00000010", + "fields": [ + [ + "PRO_CAN_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_RTC_CORE_INTR_MAP", + "offset": "0x1BC", + "reset": "0x00000010", + "fields": [ + [ + "PRO_RTC_CORE_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_RMT_INTR_MAP", + "offset": "0x1C0", + "reset": "0x00000010", + "fields": [ + [ + "PRO_RMT_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_PCNT_INTR_MAP", + "offset": "0x1C4", + "reset": "0x00000010", + "fields": [ + [ + "PRO_PCNT_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_I2C_EXT0_INTR_MAP", + "offset": "0x1C8", + "reset": "0x00000010", + "fields": [ + [ + "PRO_I2C_EXT0_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_I2C_EXT1_INTR_MAP", + "offset": "0x1CC", + "reset": "0x00000010", + "fields": [ + [ + "PRO_I2C_EXT1_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_RSA_INTR_MAP", + "offset": "0x1D0", + "reset": "0x00000010", + "fields": [ + [ + "PRO_RSA_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_SPI1_DMA_INT_MAP", + "offset": "0x1D4", + "reset": "0x00000010", + "fields": [ + [ + "PRO_SPI1_DMA_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_SPI2_DMA_INT_MAP", + "offset": "0x1D8", + "reset": "0x00000010", + "fields": [ + [ + "PRO_SPI2_DMA_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_SPI3_DMA_INT_MAP", + "offset": "0x1DC", + "reset": "0x00000010", + "fields": [ + [ + "PRO_SPI3_DMA_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_WDG_INT_MAP", + "offset": "0x1E0", + "reset": "0x00000010", + "fields": [ + [ + "PRO_WDG_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TIMER_INT1_MAP", + "offset": "0x1E4", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TIMER_INT1_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TIMER_INT2_MAP", + "offset": "0x1E8", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TIMER_INT2_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TG_T0_EDGE_INT_MAP", + "offset": "0x1EC", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TG_T0_EDGE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TG_T1_EDGE_INT_MAP", + "offset": "0x1F0", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TG_T1_EDGE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TG_WDT_EDGE_INT_MAP", + "offset": "0x1F4", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TG_WDT_EDGE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TG_LACT_EDGE_INT_MAP", + "offset": "0x1F8", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TG_LACT_EDGE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TG1_T0_EDGE_INT_MAP", + "offset": "0x1FC", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TG1_T0_EDGE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TG1_T1_EDGE_INT_MAP", + "offset": "0x200", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TG1_T1_EDGE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TG1_WDT_EDGE_INT_MAP", + "offset": "0x204", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TG1_WDT_EDGE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_TG1_LACT_EDGE_INT_MAP", + "offset": "0x208", + "reset": "0x00000010", + "fields": [ + [ + "PRO_TG1_LACT_EDGE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_MMU_IA_INT_MAP", + "offset": "0x20C", + "reset": "0x00000010", + "fields": [ + [ + "PRO_MMU_IA_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_MPU_IA_INT_MAP", + "offset": "0x210", + "reset": "0x00000010", + "fields": [ + [ + "PRO_MPU_IA_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "PRO_CACHE_IA_INT_MAP", + "offset": "0x214", + "reset": "0x00000010", + "fields": [ + [ + "PRO_CACHE_IA_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_MAC_INTR_MAP", + "offset": "0x218", + "reset": "0x00000010", + "fields": [ + [ + "APP_MAC_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_MAC_NMI_MAP", + "offset": "0x21C", + "reset": "0x00000010", + "fields": [ + [ + "APP_MAC_NMI_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_BB_INT_MAP", + "offset": "0x220", + "reset": "0x00000010", + "fields": [ + [ + "APP_BB_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_BT_MAC_INT_MAP", + "offset": "0x224", + "reset": "0x00000010", + "fields": [ + [ + "APP_BT_MAC_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_BT_BB_INT_MAP", + "offset": "0x228", + "reset": "0x00000010", + "fields": [ + [ + "APP_BT_BB_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_BT_BB_NMI_MAP", + "offset": "0x22C", + "reset": "0x00000010", + "fields": [ + [ + "APP_BT_BB_NMI_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_RWBT_IRQ_MAP", + "offset": "0x230", + "reset": "0x00000010", + "fields": [ + [ + "APP_RWBT_IRQ_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_RWBLE_IRQ_MAP", + "offset": "0x234", + "reset": "0x00000010", + "fields": [ + [ + "APP_RWBLE_IRQ_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_RWBT_NMI_MAP", + "offset": "0x238", + "reset": "0x00000010", + "fields": [ + [ + "APP_RWBT_NMI_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_RWBLE_NMI_MAP", + "offset": "0x23C", + "reset": "0x00000010", + "fields": [ + [ + "APP_RWBLE_NMI_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_SLC0_INTR_MAP", + "offset": "0x240", + "reset": "0x00000010", + "fields": [ + [ + "APP_SLC0_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_SLC1_INTR_MAP", + "offset": "0x244", + "reset": "0x00000010", + "fields": [ + [ + "APP_SLC1_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_UHCI0_INTR_MAP", + "offset": "0x248", + "reset": "0x00000010", + "fields": [ + [ + "APP_UHCI0_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_UHCI1_INTR_MAP", + "offset": "0x24C", + "reset": "0x00000010", + "fields": [ + [ + "APP_UHCI1_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TG_T0_LEVEL_INT_MAP", + "offset": "0x250", + "reset": "0x00000010", + "fields": [ + [ + "APP_TG_T0_LEVEL_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TG_T1_LEVEL_INT_MAP", + "offset": "0x254", + "reset": "0x00000010", + "fields": [ + [ + "APP_TG_T1_LEVEL_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TG_WDT_LEVEL_INT_MAP", + "offset": "0x258", + "reset": "0x00000010", + "fields": [ + [ + "APP_TG_WDT_LEVEL_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TG_LACT_LEVEL_INT_MAP", + "offset": "0x25C", + "reset": "0x00000010", + "fields": [ + [ + "APP_TG_LACT_LEVEL_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TG1_T0_LEVEL_INT_MAP", + "offset": "0x260", + "reset": "0x00000010", + "fields": [ + [ + "APP_TG1_T0_LEVEL_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TG1_T1_LEVEL_INT_MAP", + "offset": "0x264", + "reset": "0x00000010", + "fields": [ + [ + "APP_TG1_T1_LEVEL_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TG1_WDT_LEVEL_INT_MAP", + "offset": "0x268", + "reset": "0x00000010", + "fields": [ + [ + "APP_TG1_WDT_LEVEL_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TG1_LACT_LEVEL_INT_MAP", + "offset": "0x26C", + "reset": "0x00000010", + "fields": [ + [ + "APP_TG1_LACT_LEVEL_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_GPIO_INTERRUPT_MAP", + "offset": "0x270", + "reset": "0x00000010", + "fields": [ + [ + "APP_GPIO_INTERRUPT_APP_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_GPIO_INTERRUPT_NMI_MAP", + "offset": "0x274", + "reset": "0x00000010", + "fields": [ + [ + "APP_GPIO_INTERRUPT_APP_NMI_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_CPU_INTR_FROM_CPU_0_MAP", + "offset": "0x278", + "reset": "0x00000010", + "fields": [ + [ + "APP_CPU_INTR_FROM_CPU_0_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_CPU_INTR_FROM_CPU_1_MAP", + "offset": "0x27C", + "reset": "0x00000010", + "fields": [ + [ + "APP_CPU_INTR_FROM_CPU_1_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_CPU_INTR_FROM_CPU_2_MAP", + "offset": "0x280", + "reset": "0x00000010", + "fields": [ + [ + "APP_CPU_INTR_FROM_CPU_2_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_CPU_INTR_FROM_CPU_3_MAP", + "offset": "0x284", + "reset": "0x00000010", + "fields": [ + [ + "APP_CPU_INTR_FROM_CPU_3_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_SPI_INTR_0_MAP", + "offset": "0x288", + "reset": "0x00000010", + "fields": [ + [ + "APP_SPI_INTR_0_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_SPI_INTR_1_MAP", + "offset": "0x28C", + "reset": "0x00000010", + "fields": [ + [ + "APP_SPI_INTR_1_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_SPI_INTR_2_MAP", + "offset": "0x290", + "reset": "0x00000010", + "fields": [ + [ + "APP_SPI_INTR_2_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_SPI_INTR_3_MAP", + "offset": "0x294", + "reset": "0x00000010", + "fields": [ + [ + "APP_SPI_INTR_3_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_I2S0_INT_MAP", + "offset": "0x298", + "reset": "0x00000010", + "fields": [ + [ + "APP_I2S0_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_I2S1_INT_MAP", + "offset": "0x29C", + "reset": "0x00000010", + "fields": [ + [ + "APP_I2S1_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_UART_INTR_MAP", + "offset": "0x2A0", + "reset": "0x00000010", + "fields": [ + [ + "APP_UART_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_UART1_INTR_MAP", + "offset": "0x2A4", + "reset": "0x00000010", + "fields": [ + [ + "APP_UART1_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_UART2_INTR_MAP", + "offset": "0x2A8", + "reset": "0x00000010", + "fields": [ + [ + "APP_UART2_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_SDIO_HOST_INTERRUPT_MAP", + "offset": "0x2AC", + "reset": "0x00000010", + "fields": [ + [ + "APP_SDIO_HOST_INTERRUPT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_EMAC_INT_MAP", + "offset": "0x2B0", + "reset": "0x00000010", + "fields": [ + [ + "APP_EMAC_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_PWM0_INTR_MAP", + "offset": "0x2B4", + "reset": "0x00000010", + "fields": [ + [ + "APP_PWM0_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_PWM1_INTR_MAP", + "offset": "0x2B8", + "reset": "0x00000010", + "fields": [ + [ + "APP_PWM1_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_PWM2_INTR_MAP", + "offset": "0x2BC", + "reset": "0x00000010", + "fields": [ + [ + "APP_PWM2_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_PWM3_INTR_MAP", + "offset": "0x2C0", + "reset": "0x00000010", + "fields": [ + [ + "APP_PWM3_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_LEDC_INT_MAP", + "offset": "0x2C4", + "reset": "0x00000010", + "fields": [ + [ + "APP_LEDC_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_EFUSE_INT_MAP", + "offset": "0x2C8", + "reset": "0x00000010", + "fields": [ + [ + "APP_EFUSE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_CAN_INT_MAP", + "offset": "0x2CC", + "reset": "0x00000010", + "fields": [ + [ + "APP_CAN_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_RTC_CORE_INTR_MAP", + "offset": "0x2D0", + "reset": "0x00000010", + "fields": [ + [ + "APP_RTC_CORE_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_RMT_INTR_MAP", + "offset": "0x2D4", + "reset": "0x00000010", + "fields": [ + [ + "APP_RMT_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_PCNT_INTR_MAP", + "offset": "0x2D8", + "reset": "0x00000010", + "fields": [ + [ + "APP_PCNT_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_I2C_EXT0_INTR_MAP", + "offset": "0x2DC", + "reset": "0x00000010", + "fields": [ + [ + "APP_I2C_EXT0_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_I2C_EXT1_INTR_MAP", + "offset": "0x2E0", + "reset": "0x00000010", + "fields": [ + [ + "APP_I2C_EXT1_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_RSA_INTR_MAP", + "offset": "0x2E4", + "reset": "0x00000010", + "fields": [ + [ + "APP_RSA_INTR_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_SPI1_DMA_INT_MAP", + "offset": "0x2E8", + "reset": "0x00000010", + "fields": [ + [ + "APP_SPI1_DMA_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_SPI2_DMA_INT_MAP", + "offset": "0x2EC", + "reset": "0x00000010", + "fields": [ + [ + "APP_SPI2_DMA_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_SPI3_DMA_INT_MAP", + "offset": "0x2F0", + "reset": "0x00000010", + "fields": [ + [ + "APP_SPI3_DMA_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_WDG_INT_MAP", + "offset": "0x2F4", + "reset": "0x00000010", + "fields": [ + [ + "APP_WDG_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TIMER_INT1_MAP", + "offset": "0x2F8", + "reset": "0x00000010", + "fields": [ + [ + "APP_TIMER_INT1_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TIMER_INT2_MAP", + "offset": "0x2FC", + "reset": "0x00000010", + "fields": [ + [ + "APP_TIMER_INT2_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TG_T0_EDGE_INT_MAP", + "offset": "0x300", + "reset": "0x00000010", + "fields": [ + [ + "APP_TG_T0_EDGE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TG_T1_EDGE_INT_MAP", + "offset": "0x304", + "reset": "0x00000010", + "fields": [ + [ + "APP_TG_T1_EDGE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TG_WDT_EDGE_INT_MAP", + "offset": "0x308", + "reset": "0x00000010", + "fields": [ + [ + "APP_TG_WDT_EDGE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TG_LACT_EDGE_INT_MAP", + "offset": "0x30C", + "reset": "0x00000010", + "fields": [ + [ + "APP_TG_LACT_EDGE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TG1_T0_EDGE_INT_MAP", + "offset": "0x310", + "reset": "0x00000010", + "fields": [ + [ + "APP_TG1_T0_EDGE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TG1_T1_EDGE_INT_MAP", + "offset": "0x314", + "reset": "0x00000010", + "fields": [ + [ + "APP_TG1_T1_EDGE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TG1_WDT_EDGE_INT_MAP", + "offset": "0x318", + "reset": "0x00000010", + "fields": [ + [ + "APP_TG1_WDT_EDGE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_TG1_LACT_EDGE_INT_MAP", + "offset": "0x31C", + "reset": "0x00000010", + "fields": [ + [ + "APP_TG1_LACT_EDGE_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_MMU_IA_INT_MAP", + "offset": "0x320", + "reset": "0x00000010", + "fields": [ + [ + "APP_MMU_IA_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_MPU_IA_INT_MAP", + "offset": "0x324", + "reset": "0x00000010", + "fields": [ + [ + "APP_MPU_IA_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "APP_CACHE_IA_INT_MAP", + "offset": "0x328", + "reset": "0x00000010", + "fields": [ + [ + "APP_CACHE_IA_INT_MAP", + 0, + 5 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_UART", + "offset": "0x32C", + "fields": [ + [ + "UART_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_SPI1", + "offset": "0x330", + "fields": [ + [ + "SPI1_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_SPI0", + "offset": "0x334", + "fields": [ + [ + "SPI0_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_GPIO", + "offset": "0x338", + "fields": [ + [ + "GPIO_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_FE2", + "offset": "0x33C", + "fields": [ + [ + "FE2_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_FE", + "offset": "0x340", + "fields": [ + [ + "FE_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_TIMER", + "offset": "0x344", + "fields": [ + [ + "TIMER_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_RTC", + "offset": "0x348", + "fields": [ + [ + "RTC_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_IO_MUX", + "offset": "0x34C", + "fields": [ + [ + "IOMUX_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_WDG", + "offset": "0x350", + "fields": [ + [ + "WDG_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_HINF", + "offset": "0x354", + "fields": [ + [ + "HINF_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_UHCI1", + "offset": "0x358", + "fields": [ + [ + "UHCI1_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_MISC", + "offset": "0x35C", + "fields": [ + [ + "MISC_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_I2C", + "offset": "0x360", + "fields": [ + [ + "I2C_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_I2S0", + "offset": "0x364", + "fields": [ + [ + "I2S0_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_UART1", + "offset": "0x368", + "fields": [ + [ + "UART1_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_BT", + "offset": "0x36C", + "fields": [ + [ + "BT_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_BT_BUFFER", + "offset": "0x370", + "fields": [ + [ + "BTBUFFER_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_I2C_EXT0", + "offset": "0x374", + "fields": [ + [ + "I2CEXT0_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_UHCI0", + "offset": "0x378", + "fields": [ + [ + "UHCI0_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_SLCHOST", + "offset": "0x37C", + "fields": [ + [ + "SLCHOST_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_RMT", + "offset": "0x380", + "fields": [ + [ + "RMT_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_PCNT", + "offset": "0x384", + "fields": [ + [ + "PCNT_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_SLC", + "offset": "0x388", + "fields": [ + [ + "SLC_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_LEDC", + "offset": "0x38C", + "fields": [ + [ + "LEDC_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_EFUSE", + "offset": "0x390", + "fields": [ + [ + "EFUSE_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_SPI_ENCRYPT", + "offset": "0x394", + "fields": [ + [ + "SPI_ENCRYPY_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_BB", + "offset": "0x398", + "fields": [ + [ + "BB_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_PWM0", + "offset": "0x39C", + "fields": [ + [ + "PWM0_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_TIMERGROUP", + "offset": "0x3A0", + "fields": [ + [ + "TIMERGROUP_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_TIMERGROUP1", + "offset": "0x3A4", + "fields": [ + [ + "TIMERGROUP1_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_SPI2", + "offset": "0x3A8", + "fields": [ + [ + "SPI2_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_SPI3", + "offset": "0x3AC", + "fields": [ + [ + "SPI3_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_APB_CTRL", + "offset": "0x3B0", + "fields": [ + [ + "APBCTRL_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_I2C_EXT1", + "offset": "0x3B4", + "fields": [ + [ + "I2CEXT1_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_SDIO_HOST", + "offset": "0x3B8", + "fields": [ + [ + "SDIOHOST_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_EMAC", + "offset": "0x3BC", + "fields": [ + [ + "EMAC_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_CAN", + "offset": "0x3C0", + "fields": [ + [ + "CAN_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_PWM1", + "offset": "0x3C4", + "fields": [ + [ + "PWM1_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_I2S1", + "offset": "0x3C8", + "fields": [ + [ + "I2S1_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_UART2", + "offset": "0x3CC", + "fields": [ + [ + "UART2_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_PWM2", + "offset": "0x3D0", + "fields": [ + [ + "PWM2_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_PWM3", + "offset": "0x3D4", + "fields": [ + [ + "PWM3_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_RWBT", + "offset": "0x3D8", + "fields": [ + [ + "RWBT_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_BTMAC", + "offset": "0x3DC", + "fields": [ + [ + "BTMAC_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_WIFIMAC", + "offset": "0x3E0", + "fields": [ + [ + "WIFIMAC_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "AHBLITE_MPU_TABLE_PWR", + "offset": "0x3E4", + "fields": [ + [ + "PWR_ACCESS_GRANT_CONFIG", + 0, + 6 + ] + ] + }, + { + "name": "MEM_ACCESS_DBUG0", + "offset": "0x3E8", + "fields": [ + [ + "PRO_ROM_MPU_AD", + 0, + 1, + "r" + ], + [ + "PRO_ROM_IA", + 1, + 1, + "r" + ], + [ + "APP_ROM_MPU_AD", + 2, + 1, + "r" + ], + [ + "APP_ROM_IA", + 3, + 1, + "r" + ], + [ + "SHARE_ROM_MPU_AD", + 4, + 2, + "r" + ], + [ + "SHARE_ROM_IA", + 6, + 4, + "r" + ], + [ + "INTERNAL_SRAM_MMU_AD", + 10, + 4, + "r" + ], + [ + "INTERNAL_SRAM_IA", + 14, + 12, + "r" + ], + [ + "INTERNAL_SRAM_MMU_MULTI_HIT", + 26, + 4, + "r" + ] + ] + }, + { + "name": "MEM_ACCESS_DBUG1", + "offset": "0x3EC", + "fields": [ + [ + "INTERNAL_SRAM_MMU_MISS", + 0, + 4, + "r" + ], + [ + "ARB_IA", + 4, + 2, + "r" + ], + [ + "PIDGEN_IA", + 6, + 2, + "r" + ], + [ + "AHB_ACCESS_DENY", + 8, + 1, + "r" + ], + [ + "AHBLITE_ACCESS_DENY", + 9, + 1, + "r" + ], + [ + "AHBLITE_IA", + 10, + 1, + "r" + ] + ] + }, + { + "name": "PRO_DCACHE_DBUG0", + "offset": "0x3F0", + "fields": [ + [ + "PRO_SLAVE_WDATA", + 0, + 1 + ], + [ + "PRO_CACHE_MMU_IA", + 0, + 1, + "r" + ], + [ + "PRO_CACHE_IA", + 1, + 6, + "r" + ], + [ + "PRO_CACHE_STATE", + 7, + 12, + "r" + ], + [ + "PRO_WR_BAK_TO_READ", + 19, + 1, + "r" + ], + [ + "PRO_TX_END", + 20, + 1, + "r" + ], + [ + "PRO_SLAVE_WR", + 21, + 1, + "r" + ], + [ + "PRO_SLAVE_WDATA_V", + 22, + 1, + "r" + ], + [ + "PRO_RX_END", + 23, + 1, + "r" + ] + ] + }, + { + "name": "PRO_DCACHE_DBUG1", + "offset": "0x3F4", + "fields": [ + [ + "PRO_CTAG_RAM_RDATA", + 0, + 32, + "r" + ] + ] + }, + { + "name": "PRO_DCACHE_DBUG2", + "offset": "0x3F8", + "fields": [ + [ + "PRO_CACHE_VADDR", + 0, + 27, + "r" + ] + ] + }, + { + "name": "PRO_DCACHE_DBUG3", + "offset": "0x3FC", + "fields": [ + [ + "PRO_MMU_RDATA", + 0, + 9, + "r" + ], + [ + "PRO_CPU_DISABLED_CACHE_IA", + 9, + 6, + "r" + ], + [ + "PRO_CPU_DISABLED_CACHE_IA_OPPOSITE", + 9, + 1 + ], + [ + "PRO_CPU_DISABLED_CACHE_IA_DRAM1", + 10, + 1 + ], + [ + "PRO_CPU_DISABLED_CACHE_IA_IROM0", + 11, + 1 + ], + [ + "PRO_CPU_DISABLED_CACHE_IA_IRAM1", + 12, + 1 + ], + [ + "PRO_CPU_DISABLED_CACHE_IA_IRAM0", + 13, + 1 + ], + [ + "PRO_CPU_DISABLED_CACHE_IA_DROM0", + 14, + 1 + ], + [ + "PRO_CACHE_IRAM0_PID_ERROR", + 15, + 1, + "r" + ] + ] + }, + { + "name": "PRO_DCACHE_DBUG4", + "offset": "0x400", + "fields": [ + [ + "PRO_DRAM1ADDR0_IA", + 0, + 20, + "r" + ] + ] + }, + { + "name": "PRO_DCACHE_DBUG5", + "offset": "0x404", + "fields": [ + [ + "PRO_DROM0ADDR0_IA", + 0, + 20, + "r" + ] + ] + }, + { + "name": "PRO_DCACHE_DBUG6", + "offset": "0x408", + "fields": [ + [ + "PRO_IRAM0ADDR_IA", + 0, + 20, + "r" + ] + ] + }, + { + "name": "PRO_DCACHE_DBUG7", + "offset": "0x40C", + "fields": [ + [ + "PRO_IRAM1ADDR_IA", + 0, + 20, + "r" + ] + ] + }, + { + "name": "PRO_DCACHE_DBUG8", + "offset": "0x410", + "fields": [ + [ + "PRO_IROM0ADDR_IA", + 0, + 20, + "r" + ] + ] + }, + { + "name": "PRO_DCACHE_DBUG9", + "offset": "0x414", + "fields": [ + [ + "PRO_OPSDRAMADDR_IA", + 0, + 20, + "r" + ] + ] + }, + { + "name": "APP_DCACHE_DBUG0", + "offset": "0x418", + "fields": [ + [ + "APP_SLAVE_WDATA", + 0, + 1 + ], + [ + "APP_CACHE_MMU_IA", + 0, + 1, + "r" + ], + [ + "APP_CACHE_IA", + 1, + 6, + "r" + ], + [ + "APP_CACHE_STATE", + 7, + 12, + "r" + ], + [ + "APP_WR_BAK_TO_READ", + 19, + 1, + "r" + ], + [ + "APP_TX_END", + 20, + 1, + "r" + ], + [ + "APP_SLAVE_WR", + 21, + 1, + "r" + ], + [ + "APP_SLAVE_WDATA_V", + 22, + 1, + "r" + ], + [ + "APP_RX_END", + 23, + 1, + "r" + ] + ] + }, + { + "name": "APP_DCACHE_DBUG1", + "offset": "0x41C", + "fields": [ + [ + "APP_CTAG_RAM_RDATA", + 0, + 32, + "r" + ] + ] + }, + { + "name": "APP_DCACHE_DBUG2", + "offset": "0x420", + "fields": [ + [ + "APP_CACHE_VADDR", + 0, + 27, + "r" + ] + ] + }, + { + "name": "APP_DCACHE_DBUG3", + "offset": "0x424", + "fields": [ + [ + "APP_MMU_RDATA", + 0, + 9, + "r" + ], + [ + "APP_CPU_DISABLED_CACHE_IA", + 9, + 6, + "r" + ], + [ + "APP_CPU_DISABLED_CACHE_IA_OPPOSITE", + 9, + 1 + ], + [ + "APP_CPU_DISABLED_CACHE_IA_DRAM1", + 10, + 1 + ], + [ + "APP_CPU_DISABLED_CACHE_IA_IROM0", + 11, + 1 + ], + [ + "APP_CPU_DISABLED_CACHE_IA_IRAM1", + 12, + 1 + ], + [ + "APP_CPU_DISABLED_CACHE_IA_IRAM0", + 13, + 1 + ], + [ + "APP_CPU_DISABLED_CACHE_IA_DROM0", + 14, + 1 + ], + [ + "APP_CACHE_IRAM0_PID_ERROR", + 15, + 1, + "r" + ] + ] + }, + { + "name": "APP_DCACHE_DBUG4", + "offset": "0x428", + "fields": [ + [ + "APP_DRAM1ADDR0_IA", + 0, + 20, + "r" + ] + ] + }, + { + "name": "APP_DCACHE_DBUG5", + "offset": "0x42C", + "fields": [ + [ + "APP_DROM0ADDR0_IA", + 0, + 20, + "r" + ] + ] + }, + { + "name": "APP_DCACHE_DBUG6", + "offset": "0x430", + "fields": [ + [ + "APP_IRAM0ADDR_IA", + 0, + 20, + "r" + ] + ] + }, + { + "name": "APP_DCACHE_DBUG7", + "offset": "0x434", + "fields": [ + [ + "APP_IRAM1ADDR_IA", + 0, + 20, + "r" + ] + ] + }, + { + "name": "APP_DCACHE_DBUG8", + "offset": "0x438", + "fields": [ + [ + "APP_IROM0ADDR_IA", + 0, + 20, + "r" + ] + ] + }, + { + "name": "APP_DCACHE_DBUG9", + "offset": "0x43C", + "fields": [ + [ + "APP_OPSDRAMADDR_IA", + 0, + 20, + "r" + ] + ] + }, + { + "name": "PRO_CPU_RECORD_CTRL", + "offset": "0x440", + "reset": "0x00000100", + "fields": [ + [ + "PRO_CPU_RECORD_ENABLE", + 0, + 1 + ], + [ + "PRO_CPU_RECORD_DISABLE", + 4, + 1 + ], + [ + "PRO_CPU_PDEBUG_ENABLE", + 8, + 1 + ] + ] + }, + { + "name": "PRO_CPU_RECORD_STATUS", + "offset": "0x444", + "fields": [ + [ + "PRO_CPU_RECORDING", + 0, + 1, + "r" + ] + ] + }, + { + "name": "PRO_CPU_RECORD_PID", + "offset": "0x448", + "fields": [ + [ + "RECORD_PRO_PID", + 0, + 3, + "r" + ] + ] + }, + { + "name": "PRO_CPU_RECORD_PDEBUGINST", + "offset": "0x44C", + "fields": [ + [ + "RECORD_PRO_PDEBUGINST", + 0, + 32, + "r" + ], + [ + "RECORD_PDEBUGINST_SZ", + 0, + 8 + ], + [ + "RECORD_PDEBUGINST_ISRC", + 12, + 3 + ], + [ + "RECORD_PDEBUGINST_LOOP_REP", + 20, + 1 + ], + [ + "RECORD_PDEBUGINST_LOOP", + 21, + 1 + ], + [ + "RECORD_PDEBUGINST_CINTL", + 24, + 4 + ] + ] + }, + { + "name": "PRO_CPU_RECORD_PDEBUGSTATUS", + "offset": "0x450", + "fields": [ + [ + "RECORD_PRO_PDEBUGSTATUS", + 0, + 8, + "r" + ], + [ + "RECORD_PDEBUGSTATUS_BBCAUSE", + 0, + 6 + ], + [ + "RECORD_PDEBUGSTATUS_INSNTYPE", + 0, + 6 + ] + ] + }, + { + "name": "PRO_CPU_RECORD_PDEBUGDATA", + "offset": "0x454", + "fields": [ + [ + "RECORD_PRO_PDEBUGDATA", + 0, + 32, + "r" + ], + [ + "RECORD_PDEBUGDATA_DEP_OTHER", + 0, + 1 + ], + [ + "RECORD_PDEBUGDATA_EXCVEC", + 0, + 5 + ], + [ + "RECORD_PDEBUGDATA_INSNTYPE_SR", + 0, + 8 + ], + [ + "RECORD_PDEBUGDATA_INSNTYPE_RER", + 0, + 1 + ], + [ + "RECORD_PDEBUGDATA_STALL_BUFF", + 1, + 1 + ], + [ + "RECORD_PDEBUGDATA_INSNTYPE_WER", + 1, + 1 + ], + [ + "RECORD_PDEBUGDATA_STALL_BUFFCONFL", + 2, + 1 + ], + [ + "RECORD_PDEBUGDATA_INSNTYPE_ER", + 2, + 12 + ], + [ + "RECORD_PDEBUGDATA_STALL_DCM", + 3, + 1 + ], + [ + "RECORD_PDEBUGDATA_STALL_LSU", + 4, + 1 + ], + [ + "RECORD_PDEBUGDATA_STALL_ICM", + 6, + 1 + ], + [ + "RECORD_PDEBUGDATA_STALL_IRAMBUSY", + 7, + 1 + ], + [ + "RECORD_PDEBUGDATA_DEP_LSU", + 8, + 1 + ], + [ + "RECORD_PDEBUGDATA_STALL_IPIF", + 8, + 1 + ], + [ + "RECORD_PDEBUGDATA_INSNTYPE_RSR", + 8, + 1 + ], + [ + "RECORD_PDEBUGDATA_STALL_TIE", + 9, + 1 + ], + [ + "RECORD_PDEBUGDATA_INSNTYPE_WSR", + 9, + 1 + ], + [ + "RECORD_PDEBUGDATA_STALL_RUN", + 10, + 1 + ], + [ + "RECORD_PDEBUGDATA_INSNTYPE_XSR", + 10, + 1 + ], + [ + "RECORD_PDEBUGDATA_DEP_STR", + 11, + 1 + ], + [ + "RECORD_PDEBUGDATA_DEP", + 12, + 1 + ], + [ + "RECORD_PDEBUGDATA_STALL_BPIFETCH", + 12, + 1 + ], + [ + "RECORD_PDEBUGDATA_STALL_L32R", + 13, + 1 + ], + [ + "RECORD_PDEBUGDATA_STALL_LSPROC", + 14, + 1 + ], + [ + "RECORD_PDEBUGDATA_STALL_BPLOAD", + 15, + 1 + ], + [ + "RECORD_PDEBUGDATA_DEP_MEMW", + 16, + 1 + ], + [ + "RECORD_PDEBUGDATA_EXCCAUSE", + 16, + 6 + ], + [ + "RECORD_PDEBUGDATA_STALL_BANKCONFL", + 16, + 1 + ], + [ + "RECORD_PDEBUGDATA_DEP_HALT", + 17, + 1 + ], + [ + "RECORD_PDEBUGDATA_STALL_ITERMUL", + 18, + 1 + ], + [ + "RECORD_PDEBUGDATA_STALL_ITERDIV", + 19, + 1 + ] + ] + }, + { + "name": "PRO_CPU_RECORD_PDEBUGPC", + "offset": "0x458", + "fields": [ + [ + "RECORD_PRO_PDEBUGPC", + 0, + 32, + "r" + ] + ] + }, + { + "name": "PRO_CPU_RECORD_PDEBUGLS0STAT", + "offset": "0x45C", + "fields": [ + [ + "RECORD_PRO_PDEBUGLS0STAT", + 0, + 32, + "r" + ], + [ + "RECORD_PDEBUGLS0STAT_TYPE", + 0, + 4 + ], + [ + "RECORD_PDEBUGLS0STAT_SZ", + 4, + 4 + ], + [ + "RECORD_PDEBUGLS0STAT_DTLBM", + 8, + 1 + ], + [ + "RECORD_PDEBUGLS0STAT_DCM", + 9, + 1 + ], + [ + "RECORD_PDEBUGLS0STAT_DCH", + 10, + 1 + ], + [ + "RECORD_PDEBUGLS0STAT_UC", + 12, + 1 + ], + [ + "RECORD_PDEBUGLS0STAT_WB", + 13, + 1 + ], + [ + "RECORD_PDEBUGLS0STAT_COH", + 16, + 1 + ], + [ + "RECORD_PDEBUGLS0STAT_STCOH", + 17, + 2 + ], + [ + "RECORD_PDEBUGLS0STAT_TGT", + 20, + 4 + ] + ] + }, + { + "name": "PRO_CPU_RECORD_PDEBUGLS0ADDR", + "offset": "0x460", + "fields": [ + [ + "RECORD_PRO_PDEBUGLS0ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "PRO_CPU_RECORD_PDEBUGLS0DATA", + "offset": "0x464", + "fields": [ + [ + "RECORD_PRO_PDEBUGLS0DATA", + 0, + 32, + "r" + ] + ] + }, + { + "name": "APP_CPU_RECORD_CTRL", + "offset": "0x468", + "reset": "0x00000100", + "fields": [ + [ + "APP_CPU_RECORD_ENABLE", + 0, + 1 + ], + [ + "APP_CPU_RECORD_DISABLE", + 4, + 1 + ], + [ + "APP_CPU_PDEBUG_ENABLE", + 8, + 1 + ] + ] + }, + { + "name": "APP_CPU_RECORD_STATUS", + "offset": "0x46C", + "fields": [ + [ + "APP_CPU_RECORDING", + 0, + 1, + "r" + ] + ] + }, + { + "name": "APP_CPU_RECORD_PID", + "offset": "0x470", + "fields": [ + [ + "RECORD_APP_PID", + 0, + 3, + "r" + ] + ] + }, + { + "name": "APP_CPU_RECORD_PDEBUGINST", + "offset": "0x474", + "fields": [ + [ + "RECORD_APP_PDEBUGINST", + 0, + 32, + "r" + ] + ] + }, + { + "name": "APP_CPU_RECORD_PDEBUGSTATUS", + "offset": "0x478", + "fields": [ + [ + "RECORD_APP_PDEBUGSTATUS", + 0, + 8, + "r" + ] + ] + }, + { + "name": "APP_CPU_RECORD_PDEBUGDATA", + "offset": "0x47C", + "fields": [ + [ + "RECORD_APP_PDEBUGDATA", + 0, + 32, + "r" + ] + ] + }, + { + "name": "APP_CPU_RECORD_PDEBUGPC", + "offset": "0x480", + "fields": [ + [ + "RECORD_APP_PDEBUGPC", + 0, + 32, + "r" + ] + ] + }, + { + "name": "APP_CPU_RECORD_PDEBUGLS0STAT", + "offset": "0x484", + "fields": [ + [ + "RECORD_APP_PDEBUGLS0STAT", + 0, + 32, + "r" + ] + ] + }, + { + "name": "APP_CPU_RECORD_PDEBUGLS0ADDR", + "offset": "0x488", + "fields": [ + [ + "RECORD_APP_PDEBUGLS0ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "APP_CPU_RECORD_PDEBUGLS0DATA", + "offset": "0x48C", + "fields": [ + [ + "RECORD_APP_PDEBUGLS0DATA", + 0, + 32, + "r" + ] + ] + }, + { + "name": "RSA_PD_CTRL", + "offset": "0x490", + "fields": [ + [ + "RSA_PD", + 0, + 1 + ] + ] + }, + { + "name": "ROM_MPU_TABLE0", + "offset": "0x494", + "reset": "0x00000001", + "fields": [ + [ + "ROM_MPU_TABLE0", + 0, + 2 + ] + ] + }, + { + "name": "ROM_MPU_TABLE1", + "offset": "0x498", + "reset": "0x00000001", + "fields": [ + [ + "ROM_MPU_TABLE1", + 0, + 2 + ] + ] + }, + { + "name": "ROM_MPU_TABLE2", + "offset": "0x49C", + "reset": "0x00000001", + "fields": [ + [ + "ROM_MPU_TABLE2", + 0, + 2 + ] + ] + }, + { + "name": "ROM_MPU_TABLE3", + "offset": "0x4A0", + "reset": "0x00000001", + "fields": [ + [ + "ROM_MPU_TABLE3", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE0", + "offset": "0x4A4", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE0", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE1", + "offset": "0x4A8", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE1", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE2", + "offset": "0x4AC", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE2", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE3", + "offset": "0x4B0", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE3", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE4", + "offset": "0x4B4", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE4", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE5", + "offset": "0x4B8", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE5", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE6", + "offset": "0x4BC", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE6", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE7", + "offset": "0x4C0", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE7", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE8", + "offset": "0x4C4", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE8", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE9", + "offset": "0x4C8", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE9", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE10", + "offset": "0x4CC", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE10", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE11", + "offset": "0x4D0", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE11", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE12", + "offset": "0x4D4", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE12", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE13", + "offset": "0x4D8", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE13", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE14", + "offset": "0x4DC", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE14", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE15", + "offset": "0x4E0", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE15", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE16", + "offset": "0x4E4", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE16", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE17", + "offset": "0x4E8", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE17", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE18", + "offset": "0x4EC", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE18", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE19", + "offset": "0x4F0", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE19", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE20", + "offset": "0x4F4", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE20", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE21", + "offset": "0x4F8", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE21", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE22", + "offset": "0x4FC", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE22", + 0, + 2 + ] + ] + }, + { + "name": "SHROM_MPU_TABLE23", + "offset": "0x500", + "reset": "0x00000001", + "fields": [ + [ + "SHROM_MPU_TABLE23", + 0, + 2 + ] + ] + }, + { + "name": "IMMU_TABLE0", + "offset": "0x504", + "fields": [ + [ + "IMMU_TABLE0", + 0, + 7 + ] + ] + }, + { + "name": "IMMU_TABLE1", + "offset": "0x508", + "reset": "0x00000001", + "fields": [ + [ + "IMMU_TABLE1", + 0, + 7 + ] + ] + }, + { + "name": "IMMU_TABLE2", + "offset": "0x50C", + "reset": "0x00000002", + "fields": [ + [ + "IMMU_TABLE2", + 0, + 7 + ] + ] + }, + { + "name": "IMMU_TABLE3", + "offset": "0x510", + "reset": "0x00000003", + "fields": [ + [ + "IMMU_TABLE3", + 0, + 7 + ] + ] + }, + { + "name": "IMMU_TABLE4", + "offset": "0x514", + "reset": "0x00000004", + "fields": [ + [ + "IMMU_TABLE4", + 0, + 7 + ] + ] + }, + { + "name": "IMMU_TABLE5", + "offset": "0x518", + "reset": "0x00000005", + "fields": [ + [ + "IMMU_TABLE5", + 0, + 7 + ] + ] + }, + { + "name": "IMMU_TABLE6", + "offset": "0x51C", + "reset": "0x00000006", + "fields": [ + [ + "IMMU_TABLE6", + 0, + 7 + ] + ] + }, + { + "name": "IMMU_TABLE7", + "offset": "0x520", + "reset": "0x00000007", + "fields": [ + [ + "IMMU_TABLE7", + 0, + 7 + ] + ] + }, + { + "name": "IMMU_TABLE8", + "offset": "0x524", + "reset": "0x00000008", + "fields": [ + [ + "IMMU_TABLE8", + 0, + 7 + ] + ] + }, + { + "name": "IMMU_TABLE9", + "offset": "0x528", + "reset": "0x00000009", + "fields": [ + [ + "IMMU_TABLE9", + 0, + 7 + ] + ] + }, + { + "name": "IMMU_TABLE10", + "offset": "0x52C", + "reset": "0x0000000A", + "fields": [ + [ + "IMMU_TABLE10", + 0, + 7 + ] + ] + }, + { + "name": "IMMU_TABLE11", + "offset": "0x530", + "reset": "0x0000000B", + "fields": [ + [ + "IMMU_TABLE11", + 0, + 7 + ] + ] + }, + { + "name": "IMMU_TABLE12", + "offset": "0x534", + "reset": "0x0000000C", + "fields": [ + [ + "IMMU_TABLE12", + 0, + 7 + ] + ] + }, + { + "name": "IMMU_TABLE13", + "offset": "0x538", + "reset": "0x0000000D", + "fields": [ + [ + "IMMU_TABLE13", + 0, + 7 + ] + ] + }, + { + "name": "IMMU_TABLE14", + "offset": "0x53C", + "reset": "0x0000000E", + "fields": [ + [ + "IMMU_TABLE14", + 0, + 7 + ] + ] + }, + { + "name": "IMMU_TABLE15", + "offset": "0x540", + "reset": "0x0000000F", + "fields": [ + [ + "IMMU_TABLE15", + 0, + 7 + ] + ] + }, + { + "name": "DMMU_TABLE0", + "offset": "0x544", + "fields": [ + [ + "DMMU_TABLE0", + 0, + 7 + ] + ] + }, + { + "name": "DMMU_TABLE1", + "offset": "0x548", + "reset": "0x00000001", + "fields": [ + [ + "DMMU_TABLE1", + 0, + 7 + ] + ] + }, + { + "name": "DMMU_TABLE2", + "offset": "0x54C", + "reset": "0x00000002", + "fields": [ + [ + "DMMU_TABLE2", + 0, + 7 + ] + ] + }, + { + "name": "DMMU_TABLE3", + "offset": "0x550", + "reset": "0x00000003", + "fields": [ + [ + "DMMU_TABLE3", + 0, + 7 + ] + ] + }, + { + "name": "DMMU_TABLE4", + "offset": "0x554", + "reset": "0x00000004", + "fields": [ + [ + "DMMU_TABLE4", + 0, + 7 + ] + ] + }, + { + "name": "DMMU_TABLE5", + "offset": "0x558", + "reset": "0x00000005", + "fields": [ + [ + "DMMU_TABLE5", + 0, + 7 + ] + ] + }, + { + "name": "DMMU_TABLE6", + "offset": "0x55C", + "reset": "0x00000006", + "fields": [ + [ + "DMMU_TABLE6", + 0, + 7 + ] + ] + }, + { + "name": "DMMU_TABLE7", + "offset": "0x560", + "reset": "0x00000007", + "fields": [ + [ + "DMMU_TABLE7", + 0, + 7 + ] + ] + }, + { + "name": "DMMU_TABLE8", + "offset": "0x564", + "reset": "0x00000008", + "fields": [ + [ + "DMMU_TABLE8", + 0, + 7 + ] + ] + }, + { + "name": "DMMU_TABLE9", + "offset": "0x568", + "reset": "0x00000009", + "fields": [ + [ + "DMMU_TABLE9", + 0, + 7 + ] + ] + }, + { + "name": "DMMU_TABLE10", + "offset": "0x56C", + "reset": "0x0000000A", + "fields": [ + [ + "DMMU_TABLE10", + 0, + 7 + ] + ] + }, + { + "name": "DMMU_TABLE11", + "offset": "0x570", + "reset": "0x0000000B", + "fields": [ + [ + "DMMU_TABLE11", + 0, + 7 + ] + ] + }, + { + "name": "DMMU_TABLE12", + "offset": "0x574", + "reset": "0x0000000C", + "fields": [ + [ + "DMMU_TABLE12", + 0, + 7 + ] + ] + }, + { + "name": "DMMU_TABLE13", + "offset": "0x578", + "reset": "0x0000000D", + "fields": [ + [ + "DMMU_TABLE13", + 0, + 7 + ] + ] + }, + { + "name": "DMMU_TABLE14", + "offset": "0x57C", + "reset": "0x0000000E", + "fields": [ + [ + "DMMU_TABLE14", + 0, + 7 + ] + ] + }, + { + "name": "DMMU_TABLE15", + "offset": "0x580", + "reset": "0x0000000F", + "fields": [ + [ + "DMMU_TABLE15", + 0, + 7 + ] + ] + }, + { + "name": "PRO_INTRUSION_CTRL", + "offset": "0x584", + "reset": "0x00000001", + "fields": [ + [ + "PRO_INTRUSION_RECORD_RESET_N", + 0, + 1 + ] + ] + }, + { + "name": "PRO_INTRUSION_STATUS", + "offset": "0x588", + "fields": [ + [ + "PRO_INTRUSION_RECORD", + 0, + 4, + "r" + ] + ] + }, + { + "name": "APP_INTRUSION_CTRL", + "offset": "0x58C", + "reset": "0x00000001", + "fields": [ + [ + "APP_INTRUSION_RECORD_RESET_N", + 0, + 1 + ] + ] + }, + { + "name": "APP_INTRUSION_STATUS", + "offset": "0x590", + "fields": [ + [ + "APP_INTRUSION_RECORD", + 0, + 4, + "r" + ] + ] + }, + { + "name": "FRONT_END_MEM_PD", + "offset": "0x594", + "reset": "0x00000005", + "fields": [ + [ + "AGC_MEM_FORCE_PU", + 0, + 1 + ], + [ + "AGC_MEM_FORCE_PD", + 1, + 1 + ], + [ + "PBUS_MEM_FORCE_PU", + 2, + 1 + ], + [ + "PBUS_MEM_FORCE_PD", + 3, + 1 + ] + ] + }, + { + "name": "MMU_IA_INT_EN", + "offset": "0x598", + "fields": [ + [ + "MMU_IA_INT_EN", + 0, + 24 + ] + ] + }, + { + "name": "MPU_IA_INT_EN", + "offset": "0x59C", + "fields": [ + [ + "MPU_IA_INT_EN", + 0, + 17 + ] + ] + }, + { + "name": "CACHE_IA_INT_EN", + "offset": "0x5A0", + "fields": [ + [ + "CACHE_IA_INT_EN", + 0, + 28 + ], + [ + "CACHE_IA_INT_APP_DROM0", + 0, + 1 + ], + [ + "CACHE_IA_INT_APP_IRAM0", + 1, + 1 + ], + [ + "CACHE_IA_INT_APP_IRAM1", + 2, + 1 + ], + [ + "CACHE_IA_INT_APP_IROM0", + 3, + 1 + ], + [ + "CACHE_IA_INT_APP_DRAM1", + 4, + 1 + ], + [ + "CACHE_IA_INT_APP_OPPOSITE", + 5, + 1 + ], + [ + "CACHE_IA_INT_PRO_DROM0", + 14, + 1 + ], + [ + "CACHE_IA_INT_PRO_IRAM0", + 15, + 1 + ], + [ + "CACHE_IA_INT_PRO_IRAM1", + 16, + 1 + ], + [ + "CACHE_IA_INT_PRO_IROM0", + 17, + 1 + ], + [ + "CACHE_IA_INT_PRO_DRAM1", + 18, + 1 + ], + [ + "CACHE_IA_INT_PRO_OPPOSITE", + 19, + 1 + ] + ] + }, + { + "name": "SECURE_BOOT_CTRL", + "offset": "0x5A4", + "fields": [ + [ + "SW_BOOTLOADER_SEL", + 0, + 1 + ] + ] + }, + { + "name": "SPI_DMA_CHAN_SEL", + "offset": "0x5A8", + "fields": [ + [ + "SPI1_DMA_CHAN_SEL", + 0, + 2 + ], + [ + "SPI2_DMA_CHAN_SEL", + 2, + 2 + ], + [ + "SPI3_DMA_CHAN_SEL", + 4, + 2 + ] + ] + }, + { + "name": "PRO_VECBASE_CTRL", + "offset": "0x5AC", + "fields": [ + [ + "PRO_OUT_VECBASE_SEL", + 0, + 2 + ] + ] + }, + { + "name": "PRO_VECBASE_SET", + "offset": "0x5B0", + "fields": [ + [ + "PRO_OUT_VECBASE", + 0, + 22 + ] + ] + }, + { + "name": "APP_VECBASE_CTRL", + "offset": "0x5B4", + "fields": [ + [ + "APP_OUT_VECBASE_SEL", + 0, + 2 + ] + ] + }, + { + "name": "APP_VECBASE_SET", + "offset": "0x5B8", + "fields": [ + [ + "APP_OUT_VECBASE", + 0, + 22 + ] + ] + }, + { + "name": "DATE", + "offset": "0xFFC", + "reset": "0x01605190", + "fields": [ + [ + "DATE", + 0, + 28 + ] + ] + } + ] + }, + { + "name": "AES", + "base": "0x3FF01000", + "registers": [ + { + "name": "START", + "offset": "0x00", + "fields": [ + [ + "START", + 0, + 1, + "w" + ] + ] + }, + { + "name": "IDLE", + "offset": "0x04", + "fields": [ + [ + "IDLE", + 0, + 1, + "r" + ] + ] + }, + { + "name": "MODE", + "offset": "0x08", + "fields": [ + [ + "MODE", + 0, + 8 + ] + ] + }, + { + "name": "KEY0", + "offset": "0x10", + "fields": [ + [ + "KEY", + 0, + 8 + ] + ] + }, + { + "name": "KEY1", + "offset": "0x14", + "fields": [ + [ + "KEY", + 0, + 8 + ] + ] + }, + { + "name": "KEY2", + "offset": "0x18", + "fields": [ + [ + "KEY", + 0, + 8 + ] + ] + }, + { + "name": "KEY3", + "offset": "0x1C", + "fields": [ + [ + "KEY", + 0, + 8 + ] + ] + }, + { + "name": "KEY4", + "offset": "0x20", + "fields": [ + [ + "KEY", + 0, + 8 + ] + ] + }, + { + "name": "KEY5", + "offset": "0x24", + "fields": [ + [ + "KEY", + 0, + 8 + ] + ] + }, + { + "name": "KEY6", + "offset": "0x28", + "fields": [ + [ + "KEY", + 0, + 8 + ] + ] + }, + { + "name": "KEY7", + "offset": "0x2C", + "fields": [ + [ + "KEY", + 0, + 8 + ] + ] + }, + { + "name": "TEXT0", + "offset": "0x30", + "fields": [ + [ + "TEXT", + 0, + 8 + ] + ] + }, + { + "name": "TEXT1", + "offset": "0x34", + "fields": [ + [ + "TEXT", + 0, + 8 + ] + ] + }, + { + "name": "TEXT2", + "offset": "0x38", + "fields": [ + [ + "TEXT", + 0, + 8 + ] + ] + }, + { + "name": "TEXT3", + "offset": "0x3C", + "fields": [ + [ + "TEXT", + 0, + 8 + ] + ] + }, + { + "name": "ENDIAN", + "offset": "0x40", + "fields": [ + [ + "ENDIAN", + 0, + 2 + ] + ] + } + ] + }, + { + "name": "RSA", + "base": "0x3FF02000", + "interrupts": [ + "RSA" + ], + "registers": [ + { + "name": "M_PRIME", + "offset": "0x800", + "fields": [ + [ + "M_PRIME", + 0, + 8 + ] + ] + }, + { + "name": "MODEXP_MODE", + "offset": "0x804", + "fields": [ + [ + "MODEXP_MODE", + 0, + 3 + ] + ] + }, + { + "name": "MODEXP_START", + "offset": "0x808", + "fields": [ + [ + "MODEXP_START", + 0, + 1, + "w" + ] + ] + }, + { + "name": "MULT_MODE", + "offset": "0x80C", + "fields": [ + [ + "MULT_MODE", + 0, + 4 + ] + ] + }, + { + "name": "MULT_START", + "offset": "0x810", + "fields": [ + [ + "MULT_START", + 0, + 1, + "w" + ] + ] + }, + { + "name": "INTERRUPT", + "offset": "0x814", + "fields": [ + [ + "INTERRUPT", + 0, + 1 + ] + ] + }, + { + "name": "CLEAN", + "offset": "0x818", + "fields": [ + [ + "CLEAN", + 0, + 1, + "r" + ] + ] + }, + { + "name": "M_MEM0", + "offset": "0x00" + }, + { + "name": "M_MEM1", + "offset": "0x04" + }, + { + "name": "M_MEM2", + "offset": "0x08" + }, + { + "name": "M_MEM3", + "offset": "0x0C" + }, + { + "name": "M_MEM4", + "offset": "0x10" + }, + { + "name": "M_MEM5", + "offset": "0x14" + }, + { + "name": "M_MEM6", + "offset": "0x18" + }, + { + "name": "M_MEM7", + "offset": "0x1C" + }, + { + "name": "M_MEM8", + "offset": "0x20" + }, + { + "name": "M_MEM9", + "offset": "0x24" + }, + { + "name": "M_MEM10", + "offset": "0x28" + }, + { + "name": "M_MEM11", + "offset": "0x2C" + }, + { + "name": "M_MEM12", + "offset": "0x30" + }, + { + "name": "M_MEM13", + "offset": "0x34" + }, + { + "name": "M_MEM14", + "offset": "0x38" + }, + { + "name": "M_MEM15", + "offset": "0x3C" + }, + { + "name": "M_MEM16", + "offset": "0x40" + }, + { + "name": "M_MEM17", + "offset": "0x44" + }, + { + "name": "M_MEM18", + "offset": "0x48" + }, + { + "name": "M_MEM19", + "offset": "0x4C" + }, + { + "name": "M_MEM20", + "offset": "0x50" + }, + { + "name": "M_MEM21", + "offset": "0x54" + }, + { + "name": "M_MEM22", + "offset": "0x58" + }, + { + "name": "M_MEM23", + "offset": "0x5C" + }, + { + "name": "M_MEM24", + "offset": "0x60" + }, + { + "name": "M_MEM25", + "offset": "0x64" + }, + { + "name": "M_MEM26", + "offset": "0x68" + }, + { + "name": "M_MEM27", + "offset": "0x6C" + }, + { + "name": "M_MEM28", + "offset": "0x70" + }, + { + "name": "M_MEM29", + "offset": "0x74" + }, + { + "name": "M_MEM30", + "offset": "0x78" + }, + { + "name": "M_MEM31", + "offset": "0x7C" + }, + { + "name": "Z_MEM0", + "offset": "0x200" + }, + { + "name": "Z_MEM1", + "offset": "0x204" + }, + { + "name": "Z_MEM2", + "offset": "0x208" + }, + { + "name": "Z_MEM3", + "offset": "0x20C" + }, + { + "name": "Z_MEM4", + "offset": "0x210" + }, + { + "name": "Z_MEM5", + "offset": "0x214" + }, + { + "name": "Z_MEM6", + "offset": "0x218" + }, + { + "name": "Z_MEM7", + "offset": "0x21C" + }, + { + "name": "Z_MEM8", + "offset": "0x220" + }, + { + "name": "Z_MEM9", + "offset": "0x224" + }, + { + "name": "Z_MEM10", + "offset": "0x228" + }, + { + "name": "Z_MEM11", + "offset": "0x22C" + }, + { + "name": "Z_MEM12", + "offset": "0x230" + }, + { + "name": "Z_MEM13", + "offset": "0x234" + }, + { + "name": "Z_MEM14", + "offset": "0x238" + }, + { + "name": "Z_MEM15", + "offset": "0x23C" + }, + { + "name": "Z_MEM16", + "offset": "0x240" + }, + { + "name": "Z_MEM17", + "offset": "0x244" + }, + { + "name": "Z_MEM18", + "offset": "0x248" + }, + { + "name": "Z_MEM19", + "offset": "0x24C" + }, + { + "name": "Z_MEM20", + "offset": "0x250" + }, + { + "name": "Z_MEM21", + "offset": "0x254" + }, + { + "name": "Z_MEM22", + "offset": "0x258" + }, + { + "name": "Z_MEM23", + "offset": "0x25C" + }, + { + "name": "Z_MEM24", + "offset": "0x260" + }, + { + "name": "Z_MEM25", + "offset": "0x264" + }, + { + "name": "Z_MEM26", + "offset": "0x268" + }, + { + "name": "Z_MEM27", + "offset": "0x26C" + }, + { + "name": "Z_MEM28", + "offset": "0x270" + }, + { + "name": "Z_MEM29", + "offset": "0x274" + }, + { + "name": "Z_MEM30", + "offset": "0x278" + }, + { + "name": "Z_MEM31", + "offset": "0x27C" + }, + { + "name": "Y_MEM0", + "offset": "0x400" + }, + { + "name": "Y_MEM1", + "offset": "0x404" + }, + { + "name": "Y_MEM2", + "offset": "0x408" + }, + { + "name": "Y_MEM3", + "offset": "0x40C" + }, + { + "name": "Y_MEM4", + "offset": "0x410" + }, + { + "name": "Y_MEM5", + "offset": "0x414" + }, + { + "name": "Y_MEM6", + "offset": "0x418" + }, + { + "name": "Y_MEM7", + "offset": "0x41C" + }, + { + "name": "Y_MEM8", + "offset": "0x420" + }, + { + "name": "Y_MEM9", + "offset": "0x424" + }, + { + "name": "Y_MEM10", + "offset": "0x428" + }, + { + "name": "Y_MEM11", + "offset": "0x42C" + }, + { + "name": "Y_MEM12", + "offset": "0x430" + }, + { + "name": "Y_MEM13", + "offset": "0x434" + }, + { + "name": "Y_MEM14", + "offset": "0x438" + }, + { + "name": "Y_MEM15", + "offset": "0x43C" + }, + { + "name": "Y_MEM16", + "offset": "0x440" + }, + { + "name": "Y_MEM17", + "offset": "0x444" + }, + { + "name": "Y_MEM18", + "offset": "0x448" + }, + { + "name": "Y_MEM19", + "offset": "0x44C" + }, + { + "name": "Y_MEM20", + "offset": "0x450" + }, + { + "name": "Y_MEM21", + "offset": "0x454" + }, + { + "name": "Y_MEM22", + "offset": "0x458" + }, + { + "name": "Y_MEM23", + "offset": "0x45C" + }, + { + "name": "Y_MEM24", + "offset": "0x460" + }, + { + "name": "Y_MEM25", + "offset": "0x464" + }, + { + "name": "Y_MEM26", + "offset": "0x468" + }, + { + "name": "Y_MEM27", + "offset": "0x46C" + }, + { + "name": "Y_MEM28", + "offset": "0x470" + }, + { + "name": "Y_MEM29", + "offset": "0x474" + }, + { + "name": "Y_MEM30", + "offset": "0x478" + }, + { + "name": "Y_MEM31", + "offset": "0x47C" + }, + { + "name": "X_MEM0", + "offset": "0x600" + }, + { + "name": "X_MEM1", + "offset": "0x604" + }, + { + "name": "X_MEM2", + "offset": "0x608" + }, + { + "name": "X_MEM3", + "offset": "0x60C" + }, + { + "name": "X_MEM4", + "offset": "0x610" + }, + { + "name": "X_MEM5", + "offset": "0x614" + }, + { + "name": "X_MEM6", + "offset": "0x618" + }, + { + "name": "X_MEM7", + "offset": "0x61C" + }, + { + "name": "X_MEM8", + "offset": "0x620" + }, + { + "name": "X_MEM9", + "offset": "0x624" + }, + { + "name": "X_MEM10", + "offset": "0x628" + }, + { + "name": "X_MEM11", + "offset": "0x62C" + }, + { + "name": "X_MEM12", + "offset": "0x630" + }, + { + "name": "X_MEM13", + "offset": "0x634" + }, + { + "name": "X_MEM14", + "offset": "0x638" + }, + { + "name": "X_MEM15", + "offset": "0x63C" + }, + { + "name": "X_MEM16", + "offset": "0x640" + }, + { + "name": "X_MEM17", + "offset": "0x644" + }, + { + "name": "X_MEM18", + "offset": "0x648" + }, + { + "name": "X_MEM19", + "offset": "0x64C" + }, + { + "name": "X_MEM20", + "offset": "0x650" + }, + { + "name": "X_MEM21", + "offset": "0x654" + }, + { + "name": "X_MEM22", + "offset": "0x658" + }, + { + "name": "X_MEM23", + "offset": "0x65C" + }, + { + "name": "X_MEM24", + "offset": "0x660" + }, + { + "name": "X_MEM25", + "offset": "0x664" + }, + { + "name": "X_MEM26", + "offset": "0x668" + }, + { + "name": "X_MEM27", + "offset": "0x66C" + }, + { + "name": "X_MEM28", + "offset": "0x670" + }, + { + "name": "X_MEM29", + "offset": "0x674" + }, + { + "name": "X_MEM30", + "offset": "0x678" + }, + { + "name": "X_MEM31", + "offset": "0x67C" + } + ] + }, + { + "name": "SHA", + "base": "0x3FF03000", + "registers": [ + { + "name": "TEXT0", + "offset": "0x00", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT1", + "offset": "0x04", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT2", + "offset": "0x08", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT3", + "offset": "0x0C", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT4", + "offset": "0x10", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT5", + "offset": "0x14", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT6", + "offset": "0x18", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT7", + "offset": "0x1C", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT8", + "offset": "0x20", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT9", + "offset": "0x24", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT10", + "offset": "0x28", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT11", + "offset": "0x2C", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT12", + "offset": "0x30", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT13", + "offset": "0x34", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT14", + "offset": "0x38", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT15", + "offset": "0x3C", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT16", + "offset": "0x40", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT17", + "offset": "0x44", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT18", + "offset": "0x48", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT19", + "offset": "0x4C", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT20", + "offset": "0x50", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT21", + "offset": "0x54", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT22", + "offset": "0x58", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT23", + "offset": "0x5C", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT24", + "offset": "0x60", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT25", + "offset": "0x64", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT26", + "offset": "0x68", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT27", + "offset": "0x6C", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT28", + "offset": "0x70", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT29", + "offset": "0x74", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT30", + "offset": "0x78", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "TEXT31", + "offset": "0x7C", + "fields": [ + [ + "TEXT", + 0, + 32 + ] + ] + }, + { + "name": "SHA1_START", + "offset": "0x80", + "fields": [ + [ + "SHA1_START", + 0, + 1, + "w" + ] + ] + }, + { + "name": "SHA1_CONTINUE", + "offset": "0x84", + "fields": [ + [ + "SHA1_CONTINUE", + 0, + 1, + "w" + ] + ] + }, + { + "name": "SHA1_LOAD", + "offset": "0x88", + "fields": [ + [ + "SHA1_LOAD", + 0, + 1, + "w" + ] + ] + }, + { + "name": "SHA1_BUSY", + "offset": "0x8C", + "fields": [ + [ + "SHA1_BUSY", + 0, + 1, + "r" + ] + ] + }, + { + "name": "SHA256_START", + "offset": "0x90", + "fields": [ + [ + "SHA256_START", + 0, + 1, + "w" + ] + ] + }, + { + "name": "SHA256_LOAD", + "offset": "0x98", + "fields": [ + [ + "SHA256_LOAD", + 0, + 1, + "w" + ] + ] + }, + { + "name": "SHA256_CONTINUE", + "offset": "0x94", + "fields": [ + [ + "SHA256_CONTINUE", + 0, + 1, + "w" + ] + ] + }, + { + "name": "SHA256_BUSY", + "offset": "0x9C", + "fields": [ + [ + "SHA256_BUSY", + 0, + 1, + "r" + ] + ] + }, + { + "name": "SHA384_START", + "offset": "0xA0", + "fields": [ + [ + "SHA384_START", + 0, + 1, + "w" + ] + ] + }, + { + "name": "SHA384_CONTINUE", + "offset": "0xA4", + "fields": [ + [ + "SHA384_CONTINUE", + 0, + 1, + "w" + ] + ] + }, + { + "name": "SHA384_LOAD", + "offset": "0xA8", + "fields": [ + [ + "SHA384_LOAD", + 0, + 1, + "w" + ] + ] + }, + { + "name": "SHA384_BUSY", + "offset": "0xAC", + "fields": [ + [ + "SHA384_BUSY", + 0, + 1, + "r" + ] + ] + }, + { + "name": "SHA512_START", + "offset": "0xB0", + "fields": [ + [ + "SHA512_START", + 0, + 1, + "w" + ] + ] + }, + { + "name": "SHA512_CONTINUE", + "offset": "0xB4", + "fields": [ + [ + "SHA512_CONTINUE", + 0, + 1, + "w" + ] + ] + }, + { + "name": "SHA512_LOAD", + "offset": "0xB8", + "fields": [ + [ + "SHA512_LOAD", + 0, + 1, + "w" + ] + ] + }, + { + "name": "SHA512_BUSY", + "offset": "0xBC", + "fields": [ + [ + "SHA512_BUSY", + 0, + 1, + "r" + ] + ] + } + ] + }, + { + "name": "UART0", + "base": "0x3FF40000", + "interrupts": [ + "UART0" + ], + "registers": [ + { + "name": "FIFO", + "offset": "0x00", + "fields": [ + [ + "RXFIFO_RD_BYTE", + 0, + 8 + ] + ] + }, + { + "name": "INT_RAW", + "offset": "0x04", + "fields": [ + [ + "RXFIFO_FULL_INT_RAW", + 0, + 1, + "r" + ], + [ + "TXFIFO_EMPTY_INT_RAW", + 1, + 1, + "r" + ], + [ + "PARITY_ERR_INT_RAW", + 2, + 1, + "r" + ], + [ + "FRM_ERR_INT_RAW", + 3, + 1, + "r" + ], + [ + "RXFIFO_OVF_INT_RAW", + 4, + 1, + "r" + ], + [ + "DSR_CHG_INT_RAW", + 5, + 1, + "r" + ], + [ + "CTS_CHG_INT_RAW", + 6, + 1, + "r" + ], + [ + "BRK_DET_INT_RAW", + 7, + 1, + "r" + ], + [ + "RXFIFO_TOUT_INT_RAW", + 8, + 1, + "r" + ], + [ + "SW_XON_INT_RAW", + 9, + 1, + "r" + ], + [ + "SW_XOFF_INT_RAW", + 10, + 1, + "r" + ], + [ + "GLITCH_DET_INT_RAW", + 11, + 1, + "r" + ], + [ + "TX_BRK_DONE_INT_RAW", + 12, + 1, + "r" + ], + [ + "TX_BRK_IDLE_DONE_INT_RAW", + 13, + 1, + "r" + ], + [ + "TX_DONE_INT_RAW", + 14, + 1, + "r" + ], + [ + "RS485_PARITY_ERR_INT_RAW", + 15, + 1, + "r" + ], + [ + "RS485_FRM_ERR_INT_RAW", + 16, + 1, + "r" + ], + [ + "RS485_CLASH_INT_RAW", + 17, + 1, + "r" + ], + [ + "AT_CMD_CHAR_DET_INT_RAW", + 18, + 1, + "r" + ] + ] + }, + { + "name": "INT_ST", + "offset": "0x08", + "fields": [ + [ + "RXFIFO_FULL_INT_ST", + 0, + 1, + "r" + ], + [ + "TXFIFO_EMPTY_INT_ST", + 1, + 1, + "r" + ], + [ + "PARITY_ERR_INT_ST", + 2, + 1, + "r" + ], + [ + "FRM_ERR_INT_ST", + 3, + 1, + "r" + ], + [ + "RXFIFO_OVF_INT_ST", + 4, + 1, + "r" + ], + [ + "DSR_CHG_INT_ST", + 5, + 1, + "r" + ], + [ + "CTS_CHG_INT_ST", + 6, + 1, + "r" + ], + [ + "BRK_DET_INT_ST", + 7, + 1, + "r" + ], + [ + "RXFIFO_TOUT_INT_ST", + 8, + 1, + "r" + ], + [ + "SW_XON_INT_ST", + 9, + 1, + "r" + ], + [ + "SW_XOFF_INT_ST", + 10, + 1, + "r" + ], + [ + "GLITCH_DET_INT_ST", + 11, + 1, + "r" + ], + [ + "TX_BRK_DONE_INT_ST", + 12, + 1, + "r" + ], + [ + "TX_BRK_IDLE_DONE_INT_ST", + 13, + 1, + "r" + ], + [ + "TX_DONE_INT_ST", + 14, + 1, + "r" + ], + [ + "RS485_PARITY_ERR_INT_ST", + 15, + 1, + "r" + ], + [ + "RS485_FRM_ERR_INT_ST", + 16, + 1, + "r" + ], + [ + "RS485_CLASH_INT_ST", + 17, + 1, + "r" + ], + [ + "AT_CMD_CHAR_DET_INT_ST", + 18, + 1, + "r" + ] + ] + }, + { + "name": "INT_ENA", + "offset": "0x0C", + "fields": [ + [ + "RXFIFO_FULL_INT_ENA", + 0, + 1 + ], + [ + "TXFIFO_EMPTY_INT_ENA", + 1, + 1 + ], + [ + "PARITY_ERR_INT_ENA", + 2, + 1 + ], + [ + "FRM_ERR_INT_ENA", + 3, + 1 + ], + [ + "RXFIFO_OVF_INT_ENA", + 4, + 1 + ], + [ + "DSR_CHG_INT_ENA", + 5, + 1 + ], + [ + "CTS_CHG_INT_ENA", + 6, + 1 + ], + [ + "BRK_DET_INT_ENA", + 7, + 1 + ], + [ + "RXFIFO_TOUT_INT_ENA", + 8, + 1 + ], + [ + "SW_XON_INT_ENA", + 9, + 1 + ], + [ + "SW_XOFF_INT_ENA", + 10, + 1 + ], + [ + "GLITCH_DET_INT_ENA", + 11, + 1 + ], + [ + "TX_BRK_DONE_INT_ENA", + 12, + 1 + ], + [ + "TX_BRK_IDLE_DONE_INT_ENA", + 13, + 1 + ], + [ + "TX_DONE_INT_ENA", + 14, + 1 + ], + [ + "RS485_PARITY_ERR_INT_ENA", + 15, + 1 + ], + [ + "RS485_FRM_ERR_INT_ENA", + 16, + 1 + ], + [ + "RS485_CLASH_INT_ENA", + 17, + 1 + ], + [ + "AT_CMD_CHAR_DET_INT_ENA", + 18, + 1 + ] + ] + }, + { + "name": "INT_CLR", + "offset": "0x10", + "fields": [ + [ + "RXFIFO_FULL_INT_CLR", + 0, + 1, + "w" + ], + [ + "TXFIFO_EMPTY_INT_CLR", + 1, + 1, + "w" + ], + [ + "PARITY_ERR_INT_CLR", + 2, + 1, + "w" + ], + [ + "FRM_ERR_INT_CLR", + 3, + 1, + "w" + ], + [ + "RXFIFO_OVF_INT_CLR", + 4, + 1, + "w" + ], + [ + "DSR_CHG_INT_CLR", + 5, + 1, + "w" + ], + [ + "CTS_CHG_INT_CLR", + 6, + 1, + "w" + ], + [ + "BRK_DET_INT_CLR", + 7, + 1, + "w" + ], + [ + "RXFIFO_TOUT_INT_CLR", + 8, + 1, + "w" + ], + [ + "SW_XON_INT_CLR", + 9, + 1, + "w" + ], + [ + "SW_XOFF_INT_CLR", + 10, + 1, + "w" + ], + [ + "GLITCH_DET_INT_CLR", + 11, + 1, + "w" + ], + [ + "TX_BRK_DONE_INT_CLR", + 12, + 1, + "w" + ], + [ + "TX_BRK_IDLE_DONE_INT_CLR", + 13, + 1, + "w" + ], + [ + "TX_DONE_INT_CLR", + 14, + 1, + "w" + ], + [ + "RS485_PARITY_ERR_INT_CLR", + 15, + 1, + "w" + ], + [ + "RS485_FRM_ERR_INT_CLR", + 16, + 1, + "w" + ], + [ + "RS485_CLASH_INT_CLR", + 17, + 1, + "w" + ], + [ + "AT_CMD_CHAR_DET_INT_CLR", + 18, + 1, + "w" + ] + ] + }, + { + "name": "CLKDIV", + "offset": "0x14", + "reset": "0x000002B6", + "fields": [ + [ + "CLKDIV", + 0, + 20 + ], + [ + "FRAG", + 20, + 4 + ] + ] + }, + { + "name": "AUTOBAUD", + "offset": "0x18", + "reset": "0x00001000", + "fields": [ + [ + "EN", + 0, + 1 + ], + [ + "GLITCH_FILT", + 8, + 8 + ] + ] + }, + { + "name": "STATUS", + "offset": "0x1C", + "fields": [ + [ + "RXFIFO_CNT", + 0, + 8, + "r" + ], + [ + "ST_URX_OUT", + 8, + 4, + "r" + ], + [ + "DSRN", + 13, + 1, + "r" + ], + [ + "CTSN", + 14, + 1, + "r" + ], + [ + "RXD", + 15, + 1, + "r" + ], + [ + "TXFIFO_CNT", + 16, + 8, + "r" + ], + [ + "ST_UTX_OUT", + 24, + 4, + "r" + ], + [ + "DTRN", + 29, + 1, + "r" + ], + [ + "RTSN", + 30, + 1, + "r" + ], + [ + "TXD", + 31, + 1, + "r" + ] + ] + }, + { + "name": "CONF0", + "offset": "0x20", + "reset": "0x0800001C", + "fields": [ + [ + "PARITY", + 0, + 1 + ], + [ + "PARITY_EN", + 1, + 1 + ], + [ + "BIT_NUM", + 2, + 2 + ], + [ + "STOP_BIT_NUM", + 4, + 2 + ], + [ + "SW_RTS", + 6, + 1 + ], + [ + "SW_DTR", + 7, + 1 + ], + [ + "TXD_BRK", + 8, + 1 + ], + [ + "IRDA_DPLX", + 9, + 1 + ], + [ + "IRDA_TX_EN", + 10, + 1 + ], + [ + "IRDA_WCTL", + 11, + 1 + ], + [ + "IRDA_TX_INV", + 12, + 1 + ], + [ + "IRDA_RX_INV", + 13, + 1 + ], + [ + "LOOPBACK", + 14, + 1 + ], + [ + "TX_FLOW_EN", + 15, + 1 + ], + [ + "IRDA_EN", + 16, + 1 + ], + [ + "RXFIFO_RST", + 17, + 1 + ], + [ + "TXFIFO_RST", + 18, + 1 + ], + [ + "RXD_INV", + 19, + 1 + ], + [ + "CTS_INV", + 20, + 1 + ], + [ + "DSR_INV", + 21, + 1 + ], + [ + "TXD_INV", + 22, + 1 + ], + [ + "RTS_INV", + 23, + 1 + ], + [ + "DTR_INV", + 24, + 1 + ], + [ + "CLK_EN", + 25, + 1 + ], + [ + "ERR_WR_MASK", + 26, + 1 + ], + [ + "TICK_REF_ALWAYS_ON", + 27, + 1 + ] + ] + }, + { + "name": "CONF1", + "offset": "0x24", + "reset": "0x00006060", + "fields": [ + [ + "RXFIFO_FULL_THRHD", + 0, + 7 + ], + [ + "TXFIFO_EMPTY_THRHD", + 8, + 7 + ], + [ + "RX_FLOW_THRHD", + 16, + 7 + ], + [ + "RX_FLOW_EN", + 23, + 1 + ], + [ + "RX_TOUT_THRHD", + 24, + 7 + ], + [ + "RX_TOUT_EN", + 31, + 1 + ] + ] + }, + { + "name": "LOWPULSE", + "offset": "0x28", + "reset": "0x000FFFFF", + "fields": [ + [ + "MIN_CNT", + 0, + 20, + "r" + ] + ] + }, + { + "name": "HIGHPULSE", + "offset": "0x2C", + "reset": "0x000FFFFF", + "fields": [ + [ + "MIN_CNT", + 0, + 20, + "r" + ] + ] + }, + { + "name": "RXD_CNT", + "offset": "0x30", + "fields": [ + [ + "RXD_EDGE_CNT", + 0, + 10, + "r" + ] + ] + }, + { + "name": "FLOW_CONF", + "offset": "0x34", + "fields": [ + [ + "SW_FLOW_CON_EN", + 0, + 1 + ], + [ + "XONOFF_DEL", + 1, + 1 + ], + [ + "FORCE_XON", + 2, + 1 + ], + [ + "FORCE_XOFF", + 3, + 1 + ], + [ + "SEND_XON", + 4, + 1 + ], + [ + "SEND_XOFF", + 5, + 1 + ] + ] + }, + { + "name": "SLEEP_CONF", + "offset": "0x38", + "reset": "0x000000F0", + "fields": [ + [ + "ACTIVE_THRESHOLD", + 0, + 10 + ] + ] + }, + { + "name": "SWFC_CONF", + "offset": "0x3C", + "reset": "0x1311E000", + "fields": [ + [ + "XON_THRESHOLD", + 0, + 8 + ], + [ + "XOFF_THRESHOLD", + 8, + 8 + ], + [ + "XON_CHAR", + 16, + 8 + ], + [ + "XOFF_CHAR", + 24, + 8 + ] + ] + }, + { + "name": "IDLE_CONF", + "offset": "0x40", + "reset": "0x00A40100", + "fields": [ + [ + "RX_IDLE_THRHD", + 0, + 10 + ], + [ + "TX_IDLE_NUM", + 10, + 10 + ], + [ + "TX_BRK_NUM", + 20, + 8 + ] + ] + }, + { + "name": "RS485_CONF", + "offset": "0x44", + "fields": [ + [ + "RS485_EN", + 0, + 1 + ], + [ + "DL0_EN", + 1, + 1 + ], + [ + "DL1_EN", + 2, + 1 + ], + [ + "RS485TX_RX_EN", + 3, + 1 + ], + [ + "RS485RXBY_TX_EN", + 4, + 1 + ], + [ + "RS485_RX_DLY_NUM", + 5, + 1 + ], + [ + "RS485_TX_DLY_NUM", + 6, + 4 + ] + ] + }, + { + "name": "AT_CMD_PRECNT", + "offset": "0x48", + "reset": "0x00186A00", + "fields": [ + [ + "PRE_IDLE_NUM", + 0, + 24 + ] + ] + }, + { + "name": "AT_CMD_POSTCNT", + "offset": "0x4C", + "reset": "0x00186A00", + "fields": [ + [ + "POST_IDLE_NUM", + 0, + 24 + ] + ] + }, + { + "name": "AT_CMD_GAPTOUT", + "offset": "0x50", + "reset": "0x00001E00", + "fields": [ + [ + "RX_GAP_TOUT", + 0, + 24 + ] + ] + }, + { + "name": "AT_CMD_CHAR", + "offset": "0x54", + "reset": "0x0000032B", + "fields": [ + [ + "AT_CMD_CHAR", + 0, + 8 + ], + [ + "CHAR_NUM", + 8, + 8 + ] + ] + }, + { + "name": "MEM_CONF", + "offset": "0x58", + "reset": "0x00000088", + "fields": [ + [ + "MEM_PD", + 0, + 1 + ], + [ + "RX_SIZE", + 3, + 4 + ], + [ + "TX_SIZE", + 7, + 4 + ], + [ + "RX_FLOW_THRHD_H3", + 15, + 3 + ], + [ + "RX_TOUT_THRHD_H3", + 18, + 3 + ], + [ + "XON_THRESHOLD_H2", + 21, + 2 + ], + [ + "XOFF_THRESHOLD_H2", + 23, + 2 + ], + [ + "RX_MEM_FULL_THRHD", + 25, + 3 + ], + [ + "TX_MEM_EMPTY_THRHD", + 28, + 3 + ] + ] + }, + { + "name": "MEM_TX_STATUS", + "offset": "0x5C", + "fields": [ + [ + "MEM_TX_STATUS", + 0, + 24, + "r" + ] + ] + }, + { + "name": "MEM_RX_STATUS", + "offset": "0x60", + "fields": [ + [ + "MEM_RX_STATUS", + 0, + 24, + "r" + ], + [ + "MEM_RX_RD_ADDR", + 2, + 11, + "r" + ], + [ + "MEM_RX_WR_ADDR", + 13, + 11, + "r" + ] + ] + }, + { + "name": "MEM_CNT_STATUS", + "offset": "0x64", + "fields": [ + [ + "RX_MEM_CNT", + 0, + 3, + "r" + ], + [ + "TX_MEM_CNT", + 3, + 3, + "r" + ] + ] + }, + { + "name": "POSPULSE", + "offset": "0x68", + "reset": "0x000FFFFF", + "fields": [ + [ + "POSEDGE_MIN_CNT", + 0, + 20, + "r" + ] + ] + }, + { + "name": "NEGPULSE", + "offset": "0x6C", + "reset": "0x000FFFFF", + "fields": [ + [ + "NEGEDGE_MIN_CNT", + 0, + 20, + "r" + ] + ] + }, + { + "name": "DATE", + "offset": "0x78", + "reset": "0x15122500", + "fields": [ + [ + "DATE", + 0, + 32 + ] + ] + }, + { + "name": "ID", + "offset": "0x7C", + "reset": "0x00000500", + "fields": [ + [ + "ID", + 0, + 32 + ] + ] + } + ] + }, + { + "name": "SPI1", + "base": "0x3FF42000", + "interrupts": [ + "SPI1", + "SPI1_DMA" + ], + "derived_from": "SPI0" + }, + { + "name": "SPI0", + "base": "0x3FF43000", + "interrupts": [ + "SPI0" + ], + "registers": [ + { + "name": "CMD", + "offset": "0x00", + "fields": [ + [ + "FLASH_PER", + 16, + 1 + ], + [ + "FLASH_PES", + 17, + 1 + ], + [ + "USR", + 18, + 1 + ], + [ + "FLASH_HPM", + 19, + 1 + ], + [ + "FLASH_RES", + 20, + 1 + ], + [ + "FLASH_DP", + 21, + 1 + ], + [ + "FLASH_CE", + 22, + 1 + ], + [ + "FLASH_BE", + 23, + 1 + ], + [ + "FLASH_SE", + 24, + 1 + ], + [ + "FLASH_PP", + 25, + 1 + ], + [ + "FLASH_WRSR", + 26, + 1 + ], + [ + "FLASH_RDSR", + 27, + 1 + ], + [ + "FLASH_RDID", + 28, + 1 + ], + [ + "FLASH_WRDI", + 29, + 1 + ], + [ + "FLASH_WREN", + 30, + 1 + ], + [ + "FLASH_READ", + 31, + 1 + ] + ] + }, + { + "name": "ADDR", + "offset": "0x04" + }, + { + "name": "CTRL", + "offset": "0x08", + "reset": "0x0020A400", + "fields": [ + [ + "FCS_CRC_EN", + 10, + 1 + ], + [ + "TX_CRC_EN", + 11, + 1 + ], + [ + "WAIT_FLASH_IDLE_EN", + 12, + 1 + ], + [ + "FASTRD_MODE", + 13, + 1 + ], + [ + "FREAD_DUAL", + 14, + 1 + ], + [ + "RESANDRES", + 15, + 1 + ], + [ + "FREAD_QUAD", + 20, + 1 + ], + [ + "WP", + 21, + 1 + ], + [ + "WRSR_2B", + 22, + 1 + ], + [ + "FREAD_DIO", + 23, + 1 + ], + [ + "FREAD_QIO", + 24, + 1 + ], + [ + "RD_BIT_ORDER", + 25, + 1 + ], + [ + "WR_BIT_ORDER", + 26, + 1 + ] + ] + }, + { + "name": "CTRL1", + "offset": "0x0C", + "reset": "0x5FFF0000", + "fields": [ + [ + "CS_HOLD_DELAY_RES", + 16, + 12 + ], + [ + "CS_HOLD_DELAY", + 28, + 4 + ] + ] + }, + { + "name": "RD_STATUS", + "offset": "0x10", + "fields": [ + [ + "STATUS", + 0, + 16 + ], + [ + "WB_MODE", + 16, + 8 + ], + [ + "STATUS_EXT", + 24, + 8 + ] + ] + }, + { + "name": "CTRL2", + "offset": "0x14", + "reset": "0x00000011", + "fields": [ + [ + "SETUP_TIME", + 0, + 4 + ], + [ + "HOLD_TIME", + 4, + 4 + ], + [ + "CK_OUT_LOW_MODE", + 8, + 4 + ], + [ + "CK_OUT_HIGH_MODE", + 12, + 4 + ], + [ + "MISO_DELAY_MODE", + 16, + 2 + ], + [ + "MISO_DELAY_NUM", + 18, + 3 + ], + [ + "MOSI_DELAY_MODE", + 21, + 2 + ], + [ + "MOSI_DELAY_NUM", + 23, + 3 + ], + [ + "CS_DELAY_MODE", + 26, + 2 + ], + [ + "CS_DELAY_NUM", + 28, + 4 + ] + ] + }, + { + "name": "CLOCK", + "offset": "0x18", + "reset": "0x80003043", + "fields": [ + [ + "CLKCNT_L", + 0, + 6 + ], + [ + "CLKCNT_H", + 6, + 6 + ], + [ + "CLKCNT_N", + 12, + 6 + ], + [ + "CLKDIV_PRE", + 18, + 13 + ], + [ + "CLK_EQU_SYSCLK", + 31, + 1 + ] + ] + }, + { + "name": "USER", + "offset": "0x1C", + "reset": "0x80000040", + "fields": [ + [ + "DOUTDIN", + 0, + 1 + ], + [ + "CS_HOLD", + 4, + 1 + ], + [ + "CS_SETUP", + 5, + 1 + ], + [ + "CK_I_EDGE", + 6, + 1 + ], + [ + "CK_OUT_EDGE", + 7, + 1 + ], + [ + "RD_BYTE_ORDER", + 10, + 1 + ], + [ + "WR_BYTE_ORDER", + 11, + 1 + ], + [ + "FWRITE_DUAL", + 12, + 1 + ], + [ + "FWRITE_QUAD", + 13, + 1 + ], + [ + "FWRITE_DIO", + 14, + 1 + ], + [ + "FWRITE_QIO", + 15, + 1 + ], + [ + "SIO", + 16, + 1 + ], + [ + "USR_HOLD_POL", + 17, + 1 + ], + [ + "USR_DOUT_HOLD", + 18, + 1 + ], + [ + "USR_DIN_HOLD", + 19, + 1 + ], + [ + "USR_DUMMY_HOLD", + 20, + 1 + ], + [ + "USR_ADDR_HOLD", + 21, + 1 + ], + [ + "USR_CMD_HOLD", + 22, + 1 + ], + [ + "USR_PREP_HOLD", + 23, + 1 + ], + [ + "USR_MISO_HIGHPART", + 24, + 1 + ], + [ + "USR_MOSI_HIGHPART", + 25, + 1 + ], + [ + "USR_DUMMY_IDLE", + 26, + 1 + ], + [ + "USR_MOSI", + 27, + 1 + ], + [ + "USR_MISO", + 28, + 1 + ], + [ + "USR_DUMMY", + 29, + 1 + ], + [ + "USR_ADDR", + 30, + 1 + ], + [ + "USR_COMMAND", + 31, + 1 + ] + ] + }, + { + "name": "USER1", + "offset": "0x20", + "reset": "0x5C000007", + "fields": [ + [ + "USR_DUMMY_CYCLELEN", + 0, + 8 + ], + [ + "USR_ADDR_BITLEN", + 26, + 6, + "r" + ] + ] + }, + { + "name": "USER2", + "offset": "0x24", + "reset": "0x70000000", + "fields": [ + [ + "USR_COMMAND_VALUE", + 0, + 16 + ], + [ + "USR_COMMAND_BITLEN", + 28, + 4 + ] + ] + }, + { + "name": "MOSI_DLEN", + "offset": "0x28", + "fields": [ + [ + "USR_MOSI_DBITLEN", + 0, + 24 + ] + ] + }, + { + "name": "MISO_DLEN", + "offset": "0x2C", + "fields": [ + [ + "USR_MISO_DBITLEN", + 0, + 24 + ] + ] + }, + { + "name": "SLV_WR_STATUS", + "offset": "0x30", + "fields": [ + [ + "SLV_WR_ST", + 0, + 32 + ] + ] + }, + { + "name": "PIN", + "offset": "0x34", + "reset": "0x00000006", + "fields": [ + [ + "CS0_DIS", + 0, + 1 + ], + [ + "CS1_DIS", + 1, + 1 + ], + [ + "CS2_DIS", + 2, + 1 + ], + [ + "CK_DIS", + 5, + 1 + ], + [ + "MASTER_CS_POL", + 6, + 3 + ], + [ + "MASTER_CK_SEL", + 11, + 3 + ], + [ + "CK_IDLE_EDGE", + 29, + 1 + ], + [ + "CS_KEEP_ACTIVE", + 30, + 1 + ] + ] + }, + { + "name": "SLAVE", + "offset": "0x38", + "reset": "0x00000020", + "fields": [ + [ + "SLV_RD_BUF_DONE", + 0, + 1 + ], + [ + "SLV_WR_BUF_DONE", + 1, + 1 + ], + [ + "SLV_RD_STA_DONE", + 2, + 1 + ], + [ + "SLV_WR_STA_DONE", + 3, + 1 + ], + [ + "TRANS_DONE", + 4, + 1 + ], + [ + "INT_EN", + 5, + 5 + ], + [ + "CS_I_MODE", + 10, + 2 + ], + [ + "SLV_LAST_COMMAND", + 17, + 3, + "r" + ], + [ + "SLV_LAST_STATE", + 20, + 3, + "r" + ], + [ + "TRANS_CNT", + 23, + 4, + "r" + ], + [ + "SLV_CMD_DEFINE", + 27, + 1 + ], + [ + "SLV_WR_RD_STA_EN", + 28, + 1 + ], + [ + "SLV_WR_RD_BUF_EN", + 29, + 1 + ], + [ + "MODE", + 30, + 1 + ], + [ + "SYNC_RESET", + 31, + 1 + ] + ] + }, + { + "name": "SLAVE1", + "offset": "0x3C", + "reset": "0x02000000", + "fields": [ + [ + "SLV_RDBUF_DUMMY_EN", + 0, + 1 + ], + [ + "SLV_WRBUF_DUMMY_EN", + 1, + 1 + ], + [ + "SLV_RDSTA_DUMMY_EN", + 2, + 1 + ], + [ + "SLV_WRSTA_DUMMY_EN", + 3, + 1 + ], + [ + "SLV_WR_ADDR_BITLEN", + 4, + 6 + ], + [ + "SLV_RD_ADDR_BITLEN", + 10, + 6 + ], + [ + "SLV_STATUS_READBACK", + 25, + 1 + ], + [ + "SLV_STATUS_FAST_EN", + 26, + 1 + ], + [ + "SLV_STATUS_BITLEN", + 27, + 5 + ] + ] + }, + { + "name": "SLAVE2", + "offset": "0x40", + "fields": [ + [ + "SLV_RDSTA_DUMMY_CYCLELEN", + 0, + 8 + ], + [ + "SLV_WRSTA_DUMMY_CYCLELEN", + 8, + 8 + ], + [ + "SLV_RDBUF_DUMMY_CYCLELEN", + 16, + 8 + ], + [ + "SLV_WRBUF_DUMMY_CYCLELEN", + 24, + 8 + ] + ] + }, + { + "name": "SLAVE3", + "offset": "0x44", + "fields": [ + [ + "SLV_RDBUF_CMD_VALUE", + 0, + 8 + ], + [ + "SLV_WRBUF_CMD_VALUE", + 8, + 8 + ], + [ + "SLV_RDSTA_CMD_VALUE", + 16, + 8 + ], + [ + "SLV_WRSTA_CMD_VALUE", + 24, + 8 + ] + ] + }, + { + "name": "SLV_WRBUF_DLEN", + "offset": "0x48", + "fields": [ + [ + "SLV_WRBUF_DBITLEN", + 0, + 24 + ] + ] + }, + { + "name": "SLV_RDBUF_DLEN", + "offset": "0x4C", + "fields": [ + [ + "SLV_RDBUF_DBITLEN", + 0, + 24 + ] + ] + }, + { + "name": "CACHE_FCTRL", + "offset": "0x50", + "fields": [ + [ + "CACHE_REQ_EN", + 0, + 1 + ], + [ + "CACHE_USR_CMD_4BYTE", + 1, + 1 + ], + [ + "CACHE_FLASH_USR_CMD", + 2, + 1 + ], + [ + "CACHE_FLASH_PES_EN", + 3, + 1 + ] + ] + }, + { + "name": "CACHE_SCTRL", + "offset": "0x54", + "reset": "0x15C04830", + "fields": [ + [ + "USR_SRAM_DIO", + 1, + 1 + ], + [ + "USR_SRAM_QIO", + 2, + 1 + ], + [ + "USR_WR_SRAM_DUMMY", + 3, + 1 + ], + [ + "USR_RD_SRAM_DUMMY", + 4, + 1 + ], + [ + "CACHE_SRAM_USR_RCMD", + 5, + 1 + ], + [ + "SRAM_BYTES_LEN", + 6, + 8 + ], + [ + "SRAM_DUMMY_CYCLELEN", + 14, + 8 + ], + [ + "SRAM_ADDR_BITLEN", + 22, + 6 + ], + [ + "CACHE_SRAM_USR_WCMD", + 28, + 1 + ] + ] + }, + { + "name": "SRAM_CMD", + "offset": "0x58", + "fields": [ + [ + "SRAM_DIO", + 0, + 1 + ], + [ + "SRAM_QIO", + 1, + 1 + ], + [ + "SRAM_RSTIO", + 4, + 1 + ] + ] + }, + { + "name": "SRAM_DRD_CMD", + "offset": "0x5C", + "fields": [ + [ + "CACHE_SRAM_USR_RD_CMD_VALUE", + 0, + 16 + ], + [ + "CACHE_SRAM_USR_RD_CMD_BITLEN", + 28, + 4 + ] + ] + }, + { + "name": "SRAM_DWR_CMD", + "offset": "0x60", + "fields": [ + [ + "CACHE_SRAM_USR_WR_CMD_VALUE", + 0, + 16 + ], + [ + "CACHE_SRAM_USR_WR_CMD_BITLEN", + 28, + 4 + ] + ] + }, + { + "name": "SLV_RD_BIT", + "offset": "0x64", + "fields": [ + [ + "SLV_RDATA_BIT", + 0, + 24 + ] + ] + }, + { + "name": "W0", + "offset": "0x80", + "fields": [ + [ + "BUF0", + 0, + 32 + ] + ] + }, + { + "name": "W1", + "offset": "0x84", + "fields": [ + [ + "BUF1", + 0, + 32 + ] + ] + }, + { + "name": "W2", + "offset": "0x88", + "fields": [ + [ + "BUF2", + 0, + 32 + ] + ] + }, + { + "name": "W3", + "offset": "0x8C", + "fields": [ + [ + "BUF3", + 0, + 32 + ] + ] + }, + { + "name": "W4", + "offset": "0x90", + "fields": [ + [ + "BUF4", + 0, + 32 + ] + ] + }, + { + "name": "W5", + "offset": "0x94", + "fields": [ + [ + "BUF5", + 0, + 32 + ] + ] + }, + { + "name": "W6", + "offset": "0x98", + "fields": [ + [ + "BUF6", + 0, + 32 + ] + ] + }, + { + "name": "W7", + "offset": "0x9C", + "fields": [ + [ + "BUF7", + 0, + 32 + ] + ] + }, + { + "name": "W8", + "offset": "0xA0", + "fields": [ + [ + "BUF8", + 0, + 32 + ] + ] + }, + { + "name": "W9", + "offset": "0xA4", + "fields": [ + [ + "BUF9", + 0, + 32 + ] + ] + }, + { + "name": "W10", + "offset": "0xA8", + "fields": [ + [ + "BUF10", + 0, + 32 + ] + ] + }, + { + "name": "W11", + "offset": "0xAC", + "fields": [ + [ + "BUF11", + 0, + 32 + ] + ] + }, + { + "name": "W12", + "offset": "0xB0", + "fields": [ + [ + "BUF12", + 0, + 32 + ] + ] + }, + { + "name": "W13", + "offset": "0xB4", + "fields": [ + [ + "BUF13", + 0, + 32 + ] + ] + }, + { + "name": "W14", + "offset": "0xB8", + "fields": [ + [ + "BUF14", + 0, + 32 + ] + ] + }, + { + "name": "W15", + "offset": "0xBC", + "fields": [ + [ + "BUF15", + 0, + 32 + ] + ] + }, + { + "name": "TX_CRC", + "offset": "0xC0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "EXT0", + "offset": "0xF0", + "reset": "0x800A0050", + "fields": [ + [ + "T_PP_TIME", + 0, + 12 + ], + [ + "T_PP_SHIFT", + 16, + 4 + ], + [ + "T_PP_ENA", + 31, + 1 + ] + ] + }, + { + "name": "EXT1", + "offset": "0xF4", + "reset": "0x800F0000", + "fields": [ + [ + "T_ERASE_TIME", + 0, + 12 + ], + [ + "T_ERASE_SHIFT", + 16, + 4 + ], + [ + "T_ERASE_ENA", + 31, + 1 + ] + ] + }, + { + "name": "EXT2", + "offset": "0xF8", + "fields": [ + [ + "ST", + 0, + 3, + "r" + ] + ] + }, + { + "name": "EXT3", + "offset": "0xFC", + "fields": [ + [ + "INT_HOLD_ENA", + 0, + 2 + ] + ] + }, + { + "name": "DMA_CONF", + "offset": "0x100", + "reset": "0x00000200", + "fields": [ + [ + "IN_RST", + 2, + 1 + ], + [ + "OUT_RST", + 3, + 1 + ], + [ + "AHBM_FIFO_RST", + 4, + 1 + ], + [ + "AHBM_RST", + 5, + 1 + ], + [ + "IN_LOOP_TEST", + 6, + 1 + ], + [ + "OUT_LOOP_TEST", + 7, + 1 + ], + [ + "OUT_AUTO_WRBACK", + 8, + 1 + ], + [ + "OUT_EOF_MODE", + 9, + 1 + ], + [ + "OUTDSCR_BURST_EN", + 10, + 1 + ], + [ + "INDSCR_BURST_EN", + 11, + 1 + ], + [ + "OUT_DATA_BURST_EN", + 12, + 1 + ], + [ + "DMA_RX_STOP", + 14, + 1 + ], + [ + "DMA_TX_STOP", + 15, + 1 + ], + [ + "DMA_CONTINUE", + 16, + 1 + ] + ] + }, + { + "name": "DMA_OUT_LINK", + "offset": "0x104", + "fields": [ + [ + "OUTLINK_ADDR", + 0, + 20 + ], + [ + "OUTLINK_STOP", + 28, + 1 + ], + [ + "OUTLINK_START", + 29, + 1 + ], + [ + "OUTLINK_RESTART", + 30, + 1 + ] + ] + }, + { + "name": "DMA_IN_LINK", + "offset": "0x108", + "fields": [ + [ + "INLINK_ADDR", + 0, + 20 + ], + [ + "INLINK_AUTO_RET", + 20, + 1 + ], + [ + "INLINK_STOP", + 28, + 1 + ], + [ + "INLINK_START", + 29, + 1 + ], + [ + "INLINK_RESTART", + 30, + 1 + ] + ] + }, + { + "name": "DMA_STATUS", + "offset": "0x10C", + "fields": [ + [ + "DMA_RX_EN", + 0, + 1, + "r" + ], + [ + "DMA_TX_EN", + 1, + 1, + "r" + ] + ] + }, + { + "name": "DMA_INT_ENA", + "offset": "0x110", + "fields": [ + [ + "INLINK_DSCR_EMPTY_INT_ENA", + 0, + 1 + ], + [ + "OUTLINK_DSCR_ERROR_INT_ENA", + 1, + 1 + ], + [ + "INLINK_DSCR_ERROR_INT_ENA", + 2, + 1 + ], + [ + "IN_DONE_INT_ENA", + 3, + 1 + ], + [ + "IN_ERR_EOF_INT_ENA", + 4, + 1 + ], + [ + "IN_SUC_EOF_INT_ENA", + 5, + 1 + ], + [ + "OUT_DONE_INT_ENA", + 6, + 1 + ], + [ + "OUT_EOF_INT_ENA", + 7, + 1 + ], + [ + "OUT_TOTAL_EOF_INT_ENA", + 8, + 1 + ] + ] + }, + { + "name": "DMA_INT_RAW", + "offset": "0x114", + "fields": [ + [ + "INLINK_DSCR_EMPTY_INT_RAW", + 0, + 1, + "r" + ], + [ + "OUTLINK_DSCR_ERROR_INT_RAW", + 1, + 1, + "r" + ], + [ + "INLINK_DSCR_ERROR_INT_RAW", + 2, + 1, + "r" + ], + [ + "IN_DONE_INT_RAW", + 3, + 1, + "r" + ], + [ + "IN_ERR_EOF_INT_RAW", + 4, + 1, + "r" + ], + [ + "IN_SUC_EOF_INT_RAW", + 5, + 1, + "r" + ], + [ + "OUT_DONE_INT_RAW", + 6, + 1, + "r" + ], + [ + "OUT_EOF_INT_RAW", + 7, + 1, + "r" + ], + [ + "OUT_TOTAL_EOF_INT_RAW", + 8, + 1, + "r" + ] + ] + }, + { + "name": "DMA_INT_ST", + "offset": "0x118", + "fields": [ + [ + "INLINK_DSCR_EMPTY_INT_ST", + 0, + 1, + "r" + ], + [ + "OUTLINK_DSCR_ERROR_INT_ST", + 1, + 1, + "r" + ], + [ + "INLINK_DSCR_ERROR_INT_ST", + 2, + 1, + "r" + ], + [ + "IN_DONE_INT_ST", + 3, + 1, + "r" + ], + [ + "IN_ERR_EOF_INT_ST", + 4, + 1, + "r" + ], + [ + "IN_SUC_EOF_INT_ST", + 5, + 1, + "r" + ], + [ + "OUT_DONE_INT_ST", + 6, + 1, + "r" + ], + [ + "OUT_EOF_INT_ST", + 7, + 1, + "r" + ], + [ + "OUT_TOTAL_EOF_INT_ST", + 8, + 1, + "r" + ] + ] + }, + { + "name": "DMA_INT_CLR", + "offset": "0x11C", + "fields": [ + [ + "INLINK_DSCR_EMPTY_INT_CLR", + 0, + 1 + ], + [ + "OUTLINK_DSCR_ERROR_INT_CLR", + 1, + 1 + ], + [ + "INLINK_DSCR_ERROR_INT_CLR", + 2, + 1 + ], + [ + "IN_DONE_INT_CLR", + 3, + 1 + ], + [ + "IN_ERR_EOF_INT_CLR", + 4, + 1 + ], + [ + "IN_SUC_EOF_INT_CLR", + 5, + 1 + ], + [ + "OUT_DONE_INT_CLR", + 6, + 1 + ], + [ + "OUT_EOF_INT_CLR", + 7, + 1 + ], + [ + "OUT_TOTAL_EOF_INT_CLR", + 8, + 1 + ] + ] + }, + { + "name": "IN_ERR_EOF_DES_ADDR", + "offset": "0x120", + "fields": [ + [ + "DMA_IN_ERR_EOF_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "IN_SUC_EOF_DES_ADDR", + "offset": "0x124", + "fields": [ + [ + "DMA_IN_SUC_EOF_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "INLINK_DSCR", + "offset": "0x128", + "fields": [ + [ + "DMA_INLINK_DSCR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "INLINK_DSCR_BF0", + "offset": "0x12C", + "fields": [ + [ + "DMA_INLINK_DSCR_BF0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "INLINK_DSCR_BF1", + "offset": "0x130", + "fields": [ + [ + "DMA_INLINK_DSCR_BF1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "OUT_EOF_BFR_DES_ADDR", + "offset": "0x134", + "fields": [ + [ + "DMA_OUT_EOF_BFR_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "OUT_EOF_DES_ADDR", + "offset": "0x138", + "fields": [ + [ + "DMA_OUT_EOF_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "OUTLINK_DSCR", + "offset": "0x13C", + "fields": [ + [ + "DMA_OUTLINK_DSCR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "OUTLINK_DSCR_BF0", + "offset": "0x140", + "fields": [ + [ + "DMA_OUTLINK_DSCR_BF0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "OUTLINK_DSCR_BF1", + "offset": "0x144", + "fields": [ + [ + "DMA_OUTLINK_DSCR_BF1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DMA_RSTATUS", + "offset": "0x148", + "fields": [ + [ + "DMA_OUT_STATUS", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DMA_TSTATUS", + "offset": "0x14C", + "fields": [ + [ + "DMA_IN_STATUS", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DATE", + "offset": "0x3FC", + "reset": "0x01604270", + "fields": [ + [ + "DATE", + 0, + 28, + "r" + ] + ] + } + ] + }, + { + "name": "GPIO", + "base": "0x3FF44000", + "interrupts": [ + "GPIO", + "GPIO_NMI" + ], + "registers": [ + { + "name": "BT_SELECT", + "offset": "0x00", + "fields": [ + [ + "BT_SEL", + 0, + 32 + ] + ] + }, + { + "name": "OUT", + "offset": "0x04", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "OUT_W1TS", + "offset": "0x08", + "fields": [ + [ + "OUT_DATA_W1TS", + 0, + 32 + ] + ] + }, + { + "name": "OUT_W1TC", + "offset": "0x0C", + "fields": [ + [ + "OUT_DATA_W1TC", + 0, + 32 + ] + ] + }, + { + "name": "OUT1", + "offset": "0x10", + "fields": [ + [ + "DATA", + 0, + 8 + ] + ] + }, + { + "name": "OUT1_W1TS", + "offset": "0x14", + "fields": [ + [ + "OUT1_DATA_W1TS", + 0, + 8 + ] + ] + }, + { + "name": "OUT1_W1TC", + "offset": "0x18", + "fields": [ + [ + "OUT1_DATA_W1TC", + 0, + 8 + ] + ] + }, + { + "name": "SDIO_SELECT", + "offset": "0x1C", + "fields": [ + [ + "SDIO_SEL", + 0, + 8 + ] + ] + }, + { + "name": "ENABLE", + "offset": "0x20", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "ENABLE_W1TS", + "offset": "0x24", + "fields": [ + [ + "ENABLE_DATA_W1TS", + 0, + 32 + ] + ] + }, + { + "name": "ENABLE_W1TC", + "offset": "0x28", + "fields": [ + [ + "ENABLE_DATA_W1TC", + 0, + 32 + ] + ] + }, + { + "name": "ENABLE1", + "offset": "0x2C", + "fields": [ + [ + "DATA", + 0, + 8 + ] + ] + }, + { + "name": "ENABLE1_W1TS", + "offset": "0x30", + "fields": [ + [ + "ENABLE1_DATA_W1TS", + 0, + 8 + ] + ] + }, + { + "name": "ENABLE1_W1TC", + "offset": "0x34", + "fields": [ + [ + "ENABLE1_DATA_W1TC", + 0, + 8 + ] + ] + }, + { + "name": "STRAP", + "offset": "0x38", + "fields": [ + [ + "STRAPPING", + 0, + 16, + "r" + ] + ] + }, + { + "name": "IN", + "offset": "0x3C", + "fields": [ + [ + "DATA_NEXT", + 0, + 32 + ] + ] + }, + { + "name": "IN1", + "offset": "0x40", + "fields": [ + [ + "DATA_NEXT", + 0, + 8 + ] + ] + }, + { + "name": "STATUS", + "offset": "0x44", + "fields": [ + [ + "INT", + 0, + 32 + ] + ] + }, + { + "name": "STATUS_W1TS", + "offset": "0x48", + "fields": [ + [ + "STATUS_INT_W1TS", + 0, + 32 + ] + ] + }, + { + "name": "STATUS_W1TC", + "offset": "0x4C", + "fields": [ + [ + "STATUS_INT_W1TC", + 0, + 32 + ] + ] + }, + { + "name": "STATUS1", + "offset": "0x50", + "fields": [ + [ + "INT", + 0, + 8 + ] + ] + }, + { + "name": "STATUS1_W1TS", + "offset": "0x54", + "fields": [ + [ + "STATUS1_INT_W1TS", + 0, + 8 + ] + ] + }, + { + "name": "STATUS1_W1TC", + "offset": "0x58", + "fields": [ + [ + "STATUS1_INT_W1TC", + 0, + 8 + ] + ] + }, + { + "name": "ACPU_INT", + "offset": "0x60", + "fields": [ + [ + "APPCPU_INT", + 0, + 32, + "r" + ] + ] + }, + { + "name": "ACPU_NMI_INT", + "offset": "0x64", + "fields": [ + [ + "APPCPU_NMI_INT", + 0, + 32, + "r" + ] + ] + }, + { + "name": "PCPU_INT", + "offset": "0x68", + "fields": [ + [ + "PROCPU_INT", + 0, + 32, + "r" + ] + ] + }, + { + "name": "PCPU_NMI_INT", + "offset": "0x6C", + "fields": [ + [ + "PROCPU_NMI_INT", + 0, + 32, + "r" + ] + ] + }, + { + "name": "CPUSDIO_INT", + "offset": "0x70", + "fields": [ + [ + "SDIO_INT", + 0, + 32, + "r" + ] + ] + }, + { + "name": "ACPU_INT1", + "offset": "0x74", + "fields": [ + [ + "APPCPU_INT_H", + 0, + 8, + "r" + ] + ] + }, + { + "name": "ACPU_NMI_INT1", + "offset": "0x78", + "fields": [ + [ + "APPCPU_NMI_INT_H", + 0, + 8, + "r" + ] + ] + }, + { + "name": "PCPU_INT1", + "offset": "0x7C", + "fields": [ + [ + "PROCPU_INT_H", + 0, + 8, + "r" + ] + ] + }, + { + "name": "PCPU_NMI_INT1", + "offset": "0x80", + "fields": [ + [ + "PROCPU_NMI_INT_H", + 0, + 8, + "r" + ] + ] + }, + { + "name": "CPUSDIO_INT1", + "offset": "0x84", + "fields": [ + [ + "SDIO_INT_H", + 0, + 8, + "r" + ], + [ + "PIN_PAD_DRIVER", + 2, + 1 + ], + [ + "PIN_INT_TYPE", + 7, + 3 + ], + [ + "PIN_WAKEUP_ENABLE", + 10, + 1 + ], + [ + "PIN_CONFIG", + 11, + 2 + ], + [ + "PIN_INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN0", + "offset": "0x88", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN1", + "offset": "0x8C", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN2", + "offset": "0x90", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN3", + "offset": "0x94", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN4", + "offset": "0x98", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN5", + "offset": "0x9C", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN6", + "offset": "0xA0", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN7", + "offset": "0xA4", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN8", + "offset": "0xA8", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN9", + "offset": "0xAC", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN10", + "offset": "0xB0", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN11", + "offset": "0xB4", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN12", + "offset": "0xB8", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN13", + "offset": "0xBC", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN14", + "offset": "0xC0", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN15", + "offset": "0xC4", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN16", + "offset": "0xC8", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN17", + "offset": "0xCC", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN18", + "offset": "0xD0", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN19", + "offset": "0xD4", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN20", + "offset": "0xD8", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN21", + "offset": "0xDC", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN22", + "offset": "0xE0", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN23", + "offset": "0xE4", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN24", + "offset": "0xE8", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN25", + "offset": "0xEC", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN26", + "offset": "0xF0", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN27", + "offset": "0xF4", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN28", + "offset": "0xF8", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN29", + "offset": "0xFC", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN30", + "offset": "0x100", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN31", + "offset": "0x104", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN32", + "offset": "0x108", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN33", + "offset": "0x10C", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN34", + "offset": "0x110", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN35", + "offset": "0x114", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN36", + "offset": "0x118", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN37", + "offset": "0x11C", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN38", + "offset": "0x120", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "PIN39", + "offset": "0x124", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ], + [ + "CONFIG", + 11, + 2 + ], + [ + "INT_ENA", + 13, + 5 + ] + ] + }, + { + "name": "cali_conf", + "offset": "0x128", + "fields": [ + [ + "CALI_RTC_MAX", + 0, + 10 + ], + [ + "CALI_START", + 31, + 1 + ] + ] + }, + { + "name": "cali_data", + "offset": "0x12C", + "fields": [ + [ + "CALI_VALUE_SYNC2", + 0, + 20, + "r" + ], + [ + "CALI_RDY_REAL", + 30, + 1, + "r" + ], + [ + "CALI_RDY_SYNC2", + 31, + 1, + "r" + ] + ] + }, + { + "name": "FUNC0_IN_SEL_CFG", + "offset": "0x130", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC1_IN_SEL_CFG", + "offset": "0x134", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC2_IN_SEL_CFG", + "offset": "0x138", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC3_IN_SEL_CFG", + "offset": "0x13C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC4_IN_SEL_CFG", + "offset": "0x140", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC5_IN_SEL_CFG", + "offset": "0x144", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC6_IN_SEL_CFG", + "offset": "0x148", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC7_IN_SEL_CFG", + "offset": "0x14C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC8_IN_SEL_CFG", + "offset": "0x150", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC9_IN_SEL_CFG", + "offset": "0x154", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC10_IN_SEL_CFG", + "offset": "0x158", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC11_IN_SEL_CFG", + "offset": "0x15C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC12_IN_SEL_CFG", + "offset": "0x160", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC13_IN_SEL_CFG", + "offset": "0x164", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC14_IN_SEL_CFG", + "offset": "0x168", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC15_IN_SEL_CFG", + "offset": "0x16C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC16_IN_SEL_CFG", + "offset": "0x170", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC17_IN_SEL_CFG", + "offset": "0x174", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC18_IN_SEL_CFG", + "offset": "0x178", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC19_IN_SEL_CFG", + "offset": "0x17C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC20_IN_SEL_CFG", + "offset": "0x180", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC21_IN_SEL_CFG", + "offset": "0x184", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC22_IN_SEL_CFG", + "offset": "0x188", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC23_IN_SEL_CFG", + "offset": "0x18C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC24_IN_SEL_CFG", + "offset": "0x190", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC25_IN_SEL_CFG", + "offset": "0x194", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC26_IN_SEL_CFG", + "offset": "0x198", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC27_IN_SEL_CFG", + "offset": "0x19C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC28_IN_SEL_CFG", + "offset": "0x1A0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC29_IN_SEL_CFG", + "offset": "0x1A4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC30_IN_SEL_CFG", + "offset": "0x1A8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC31_IN_SEL_CFG", + "offset": "0x1AC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC32_IN_SEL_CFG", + "offset": "0x1B0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC33_IN_SEL_CFG", + "offset": "0x1B4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC34_IN_SEL_CFG", + "offset": "0x1B8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC35_IN_SEL_CFG", + "offset": "0x1BC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC36_IN_SEL_CFG", + "offset": "0x1C0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC37_IN_SEL_CFG", + "offset": "0x1C4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC38_IN_SEL_CFG", + "offset": "0x1C8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC39_IN_SEL_CFG", + "offset": "0x1CC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC40_IN_SEL_CFG", + "offset": "0x1D0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC41_IN_SEL_CFG", + "offset": "0x1D4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC42_IN_SEL_CFG", + "offset": "0x1D8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC43_IN_SEL_CFG", + "offset": "0x1DC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC44_IN_SEL_CFG", + "offset": "0x1E0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC45_IN_SEL_CFG", + "offset": "0x1E4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC46_IN_SEL_CFG", + "offset": "0x1E8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC47_IN_SEL_CFG", + "offset": "0x1EC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC48_IN_SEL_CFG", + "offset": "0x1F0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC49_IN_SEL_CFG", + "offset": "0x1F4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC50_IN_SEL_CFG", + "offset": "0x1F8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC51_IN_SEL_CFG", + "offset": "0x1FC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC52_IN_SEL_CFG", + "offset": "0x200", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC53_IN_SEL_CFG", + "offset": "0x204", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC54_IN_SEL_CFG", + "offset": "0x208", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC55_IN_SEL_CFG", + "offset": "0x20C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC56_IN_SEL_CFG", + "offset": "0x210", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC57_IN_SEL_CFG", + "offset": "0x214", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC58_IN_SEL_CFG", + "offset": "0x218", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC59_IN_SEL_CFG", + "offset": "0x21C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC60_IN_SEL_CFG", + "offset": "0x220", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC61_IN_SEL_CFG", + "offset": "0x224", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC62_IN_SEL_CFG", + "offset": "0x228", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC63_IN_SEL_CFG", + "offset": "0x22C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC64_IN_SEL_CFG", + "offset": "0x230", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC65_IN_SEL_CFG", + "offset": "0x234", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC66_IN_SEL_CFG", + "offset": "0x238", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC67_IN_SEL_CFG", + "offset": "0x23C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC68_IN_SEL_CFG", + "offset": "0x240", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC69_IN_SEL_CFG", + "offset": "0x244", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC70_IN_SEL_CFG", + "offset": "0x248", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC71_IN_SEL_CFG", + "offset": "0x24C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC72_IN_SEL_CFG", + "offset": "0x250", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC73_IN_SEL_CFG", + "offset": "0x254", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC74_IN_SEL_CFG", + "offset": "0x258", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC75_IN_SEL_CFG", + "offset": "0x25C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC76_IN_SEL_CFG", + "offset": "0x260", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC77_IN_SEL_CFG", + "offset": "0x264", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC78_IN_SEL_CFG", + "offset": "0x268", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC79_IN_SEL_CFG", + "offset": "0x26C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC80_IN_SEL_CFG", + "offset": "0x270", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC81_IN_SEL_CFG", + "offset": "0x274", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC82_IN_SEL_CFG", + "offset": "0x278", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC83_IN_SEL_CFG", + "offset": "0x27C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC84_IN_SEL_CFG", + "offset": "0x280", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC85_IN_SEL_CFG", + "offset": "0x284", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC86_IN_SEL_CFG", + "offset": "0x288", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC87_IN_SEL_CFG", + "offset": "0x28C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC88_IN_SEL_CFG", + "offset": "0x290", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC89_IN_SEL_CFG", + "offset": "0x294", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC90_IN_SEL_CFG", + "offset": "0x298", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC91_IN_SEL_CFG", + "offset": "0x29C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC92_IN_SEL_CFG", + "offset": "0x2A0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC93_IN_SEL_CFG", + "offset": "0x2A4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC94_IN_SEL_CFG", + "offset": "0x2A8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC95_IN_SEL_CFG", + "offset": "0x2AC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC96_IN_SEL_CFG", + "offset": "0x2B0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC97_IN_SEL_CFG", + "offset": "0x2B4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC98_IN_SEL_CFG", + "offset": "0x2B8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC99_IN_SEL_CFG", + "offset": "0x2BC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC100_IN_SEL_CFG", + "offset": "0x2C0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC101_IN_SEL_CFG", + "offset": "0x2C4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC102_IN_SEL_CFG", + "offset": "0x2C8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC103_IN_SEL_CFG", + "offset": "0x2CC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC104_IN_SEL_CFG", + "offset": "0x2D0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC105_IN_SEL_CFG", + "offset": "0x2D4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC106_IN_SEL_CFG", + "offset": "0x2D8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC107_IN_SEL_CFG", + "offset": "0x2DC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC108_IN_SEL_CFG", + "offset": "0x2E0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC109_IN_SEL_CFG", + "offset": "0x2E4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC110_IN_SEL_CFG", + "offset": "0x2E8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC111_IN_SEL_CFG", + "offset": "0x2EC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC112_IN_SEL_CFG", + "offset": "0x2F0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC113_IN_SEL_CFG", + "offset": "0x2F4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC114_IN_SEL_CFG", + "offset": "0x2F8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC115_IN_SEL_CFG", + "offset": "0x2FC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC116_IN_SEL_CFG", + "offset": "0x300", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC117_IN_SEL_CFG", + "offset": "0x304", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC118_IN_SEL_CFG", + "offset": "0x308", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC119_IN_SEL_CFG", + "offset": "0x30C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC120_IN_SEL_CFG", + "offset": "0x310", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC121_IN_SEL_CFG", + "offset": "0x314", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC122_IN_SEL_CFG", + "offset": "0x318", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC123_IN_SEL_CFG", + "offset": "0x31C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC124_IN_SEL_CFG", + "offset": "0x320", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC125_IN_SEL_CFG", + "offset": "0x324", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC126_IN_SEL_CFG", + "offset": "0x328", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC127_IN_SEL_CFG", + "offset": "0x32C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC128_IN_SEL_CFG", + "offset": "0x330", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC129_IN_SEL_CFG", + "offset": "0x334", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC130_IN_SEL_CFG", + "offset": "0x338", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC131_IN_SEL_CFG", + "offset": "0x33C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC132_IN_SEL_CFG", + "offset": "0x340", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC133_IN_SEL_CFG", + "offset": "0x344", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC134_IN_SEL_CFG", + "offset": "0x348", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC135_IN_SEL_CFG", + "offset": "0x34C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC136_IN_SEL_CFG", + "offset": "0x350", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC137_IN_SEL_CFG", + "offset": "0x354", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC138_IN_SEL_CFG", + "offset": "0x358", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC139_IN_SEL_CFG", + "offset": "0x35C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC140_IN_SEL_CFG", + "offset": "0x360", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC141_IN_SEL_CFG", + "offset": "0x364", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC142_IN_SEL_CFG", + "offset": "0x368", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC143_IN_SEL_CFG", + "offset": "0x36C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC144_IN_SEL_CFG", + "offset": "0x370", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC145_IN_SEL_CFG", + "offset": "0x374", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC146_IN_SEL_CFG", + "offset": "0x378", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC147_IN_SEL_CFG", + "offset": "0x37C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC148_IN_SEL_CFG", + "offset": "0x380", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC149_IN_SEL_CFG", + "offset": "0x384", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC150_IN_SEL_CFG", + "offset": "0x388", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC151_IN_SEL_CFG", + "offset": "0x38C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC152_IN_SEL_CFG", + "offset": "0x390", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC153_IN_SEL_CFG", + "offset": "0x394", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC154_IN_SEL_CFG", + "offset": "0x398", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC155_IN_SEL_CFG", + "offset": "0x39C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC156_IN_SEL_CFG", + "offset": "0x3A0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC157_IN_SEL_CFG", + "offset": "0x3A4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC158_IN_SEL_CFG", + "offset": "0x3A8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC159_IN_SEL_CFG", + "offset": "0x3AC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC160_IN_SEL_CFG", + "offset": "0x3B0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC161_IN_SEL_CFG", + "offset": "0x3B4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC162_IN_SEL_CFG", + "offset": "0x3B8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC163_IN_SEL_CFG", + "offset": "0x3BC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC164_IN_SEL_CFG", + "offset": "0x3C0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC165_IN_SEL_CFG", + "offset": "0x3C4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC166_IN_SEL_CFG", + "offset": "0x3C8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC167_IN_SEL_CFG", + "offset": "0x3CC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC168_IN_SEL_CFG", + "offset": "0x3D0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC169_IN_SEL_CFG", + "offset": "0x3D4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC170_IN_SEL_CFG", + "offset": "0x3D8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC171_IN_SEL_CFG", + "offset": "0x3DC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC172_IN_SEL_CFG", + "offset": "0x3E0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC173_IN_SEL_CFG", + "offset": "0x3E4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC174_IN_SEL_CFG", + "offset": "0x3E8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC175_IN_SEL_CFG", + "offset": "0x3EC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC176_IN_SEL_CFG", + "offset": "0x3F0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC177_IN_SEL_CFG", + "offset": "0x3F4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC178_IN_SEL_CFG", + "offset": "0x3F8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC179_IN_SEL_CFG", + "offset": "0x3FC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC180_IN_SEL_CFG", + "offset": "0x400", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC181_IN_SEL_CFG", + "offset": "0x404", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC182_IN_SEL_CFG", + "offset": "0x408", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC183_IN_SEL_CFG", + "offset": "0x40C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC184_IN_SEL_CFG", + "offset": "0x410", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC185_IN_SEL_CFG", + "offset": "0x414", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC186_IN_SEL_CFG", + "offset": "0x418", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC187_IN_SEL_CFG", + "offset": "0x41C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC188_IN_SEL_CFG", + "offset": "0x420", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC189_IN_SEL_CFG", + "offset": "0x424", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC190_IN_SEL_CFG", + "offset": "0x428", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC191_IN_SEL_CFG", + "offset": "0x42C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC192_IN_SEL_CFG", + "offset": "0x430", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC193_IN_SEL_CFG", + "offset": "0x434", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC194_IN_SEL_CFG", + "offset": "0x438", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC195_IN_SEL_CFG", + "offset": "0x43C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC196_IN_SEL_CFG", + "offset": "0x440", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC197_IN_SEL_CFG", + "offset": "0x444", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC198_IN_SEL_CFG", + "offset": "0x448", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC199_IN_SEL_CFG", + "offset": "0x44C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC200_IN_SEL_CFG", + "offset": "0x450", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC201_IN_SEL_CFG", + "offset": "0x454", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC202_IN_SEL_CFG", + "offset": "0x458", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC203_IN_SEL_CFG", + "offset": "0x45C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC204_IN_SEL_CFG", + "offset": "0x460", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC205_IN_SEL_CFG", + "offset": "0x464", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC206_IN_SEL_CFG", + "offset": "0x468", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC207_IN_SEL_CFG", + "offset": "0x46C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC208_IN_SEL_CFG", + "offset": "0x470", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC209_IN_SEL_CFG", + "offset": "0x474", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC210_IN_SEL_CFG", + "offset": "0x478", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC211_IN_SEL_CFG", + "offset": "0x47C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC212_IN_SEL_CFG", + "offset": "0x480", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC213_IN_SEL_CFG", + "offset": "0x484", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC214_IN_SEL_CFG", + "offset": "0x488", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC215_IN_SEL_CFG", + "offset": "0x48C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC216_IN_SEL_CFG", + "offset": "0x490", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC217_IN_SEL_CFG", + "offset": "0x494", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC218_IN_SEL_CFG", + "offset": "0x498", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC219_IN_SEL_CFG", + "offset": "0x49C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC220_IN_SEL_CFG", + "offset": "0x4A0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC221_IN_SEL_CFG", + "offset": "0x4A4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC222_IN_SEL_CFG", + "offset": "0x4A8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC223_IN_SEL_CFG", + "offset": "0x4AC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC224_IN_SEL_CFG", + "offset": "0x4B0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC225_IN_SEL_CFG", + "offset": "0x4B4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC226_IN_SEL_CFG", + "offset": "0x4B8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC227_IN_SEL_CFG", + "offset": "0x4BC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC228_IN_SEL_CFG", + "offset": "0x4C0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC229_IN_SEL_CFG", + "offset": "0x4C4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC230_IN_SEL_CFG", + "offset": "0x4C8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC231_IN_SEL_CFG", + "offset": "0x4CC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC232_IN_SEL_CFG", + "offset": "0x4D0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC233_IN_SEL_CFG", + "offset": "0x4D4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC234_IN_SEL_CFG", + "offset": "0x4D8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC235_IN_SEL_CFG", + "offset": "0x4DC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC236_IN_SEL_CFG", + "offset": "0x4E0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC237_IN_SEL_CFG", + "offset": "0x4E4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC238_IN_SEL_CFG", + "offset": "0x4E8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC239_IN_SEL_CFG", + "offset": "0x4EC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC240_IN_SEL_CFG", + "offset": "0x4F0", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC241_IN_SEL_CFG", + "offset": "0x4F4", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC242_IN_SEL_CFG", + "offset": "0x4F8", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC243_IN_SEL_CFG", + "offset": "0x4FC", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC244_IN_SEL_CFG", + "offset": "0x500", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC245_IN_SEL_CFG", + "offset": "0x504", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC246_IN_SEL_CFG", + "offset": "0x508", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC247_IN_SEL_CFG", + "offset": "0x50C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC248_IN_SEL_CFG", + "offset": "0x510", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC249_IN_SEL_CFG", + "offset": "0x514", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC250_IN_SEL_CFG", + "offset": "0x518", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC251_IN_SEL_CFG", + "offset": "0x51C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC252_IN_SEL_CFG", + "offset": "0x520", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC253_IN_SEL_CFG", + "offset": "0x524", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC254_IN_SEL_CFG", + "offset": "0x528", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC255_IN_SEL_CFG", + "offset": "0x52C", + "fields": [ + [ + "IN_SEL", + 0, + 6 + ], + [ + "IN_INV_SEL", + 6, + 1 + ], + [ + "SEL", + 7, + 1 + ] + ] + }, + { + "name": "FUNC0_OUT_SEL_CFG", + "offset": "0x530", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC1_OUT_SEL_CFG", + "offset": "0x534", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC2_OUT_SEL_CFG", + "offset": "0x538", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC3_OUT_SEL_CFG", + "offset": "0x53C", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC4_OUT_SEL_CFG", + "offset": "0x540", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC5_OUT_SEL_CFG", + "offset": "0x544", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC6_OUT_SEL_CFG", + "offset": "0x548", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC7_OUT_SEL_CFG", + "offset": "0x54C", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC8_OUT_SEL_CFG", + "offset": "0x550", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC9_OUT_SEL_CFG", + "offset": "0x554", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC10_OUT_SEL_CFG", + "offset": "0x558", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC11_OUT_SEL_CFG", + "offset": "0x55C", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC12_OUT_SEL_CFG", + "offset": "0x560", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC13_OUT_SEL_CFG", + "offset": "0x564", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC14_OUT_SEL_CFG", + "offset": "0x568", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC15_OUT_SEL_CFG", + "offset": "0x56C", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC16_OUT_SEL_CFG", + "offset": "0x570", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC17_OUT_SEL_CFG", + "offset": "0x574", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC18_OUT_SEL_CFG", + "offset": "0x578", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC19_OUT_SEL_CFG", + "offset": "0x57C", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC20_OUT_SEL_CFG", + "offset": "0x580", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC21_OUT_SEL_CFG", + "offset": "0x584", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC22_OUT_SEL_CFG", + "offset": "0x588", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC23_OUT_SEL_CFG", + "offset": "0x58C", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC24_OUT_SEL_CFG", + "offset": "0x590", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC25_OUT_SEL_CFG", + "offset": "0x594", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC26_OUT_SEL_CFG", + "offset": "0x598", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC27_OUT_SEL_CFG", + "offset": "0x59C", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC28_OUT_SEL_CFG", + "offset": "0x5A0", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC29_OUT_SEL_CFG", + "offset": "0x5A4", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC30_OUT_SEL_CFG", + "offset": "0x5A8", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC31_OUT_SEL_CFG", + "offset": "0x5AC", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC32_OUT_SEL_CFG", + "offset": "0x5B0", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC33_OUT_SEL_CFG", + "offset": "0x5B4", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC34_OUT_SEL_CFG", + "offset": "0x5B8", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC35_OUT_SEL_CFG", + "offset": "0x5BC", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC36_OUT_SEL_CFG", + "offset": "0x5C0", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC37_OUT_SEL_CFG", + "offset": "0x5C4", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC38_OUT_SEL_CFG", + "offset": "0x5C8", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + }, + { + "name": "FUNC39_OUT_SEL_CFG", + "offset": "0x5CC", + "fields": [ + [ + "OUT_SEL", + 0, + 9 + ], + [ + "INV_SEL", + 9, + 1 + ], + [ + "OEN_SEL", + 10, + 1 + ], + [ + "OEN_INV_SEL", + 11, + 1 + ] + ] + } + ] + }, + { + "name": "GPIO_SD", + "base": "0x3FF44F00", + "registers": [ + { + "name": "SIGMADELTA0", + "offset": "0x00", + "reset": "0x0000FF00", + "fields": [ + [ + "SD0_IN", + 0, + 8 + ], + [ + "SD0_PRESCALE", + 8, + 8 + ] + ] + }, + { + "name": "SIGMADELTA1", + "offset": "0x04", + "reset": "0x0000FF00", + "fields": [ + [ + "SD0_IN", + 0, + 8 + ], + [ + "SD0_PRESCALE", + 8, + 8 + ] + ] + }, + { + "name": "SIGMADELTA2", + "offset": "0x08", + "reset": "0x0000FF00", + "fields": [ + [ + "SD0_IN", + 0, + 8 + ], + [ + "SD0_PRESCALE", + 8, + 8 + ] + ] + }, + { + "name": "SIGMADELTA3", + "offset": "0x0C", + "reset": "0x0000FF00", + "fields": [ + [ + "SD0_IN", + 0, + 8 + ], + [ + "SD0_PRESCALE", + 8, + 8 + ] + ] + }, + { + "name": "SIGMADELTA4", + "offset": "0x10", + "reset": "0x0000FF00", + "fields": [ + [ + "SD0_IN", + 0, + 8 + ], + [ + "SD0_PRESCALE", + 8, + 8 + ] + ] + }, + { + "name": "SIGMADELTA5", + "offset": "0x14", + "reset": "0x0000FF00", + "fields": [ + [ + "SD0_IN", + 0, + 8 + ], + [ + "SD0_PRESCALE", + 8, + 8 + ] + ] + }, + { + "name": "SIGMADELTA6", + "offset": "0x18", + "reset": "0x0000FF00", + "fields": [ + [ + "SD0_IN", + 0, + 8 + ], + [ + "SD0_PRESCALE", + 8, + 8 + ] + ] + }, + { + "name": "SIGMADELTA7", + "offset": "0x1C", + "reset": "0x0000FF00", + "fields": [ + [ + "SD0_IN", + 0, + 8 + ], + [ + "SD0_PRESCALE", + 8, + 8 + ] + ] + }, + { + "name": "CG", + "offset": "0x20", + "fields": [ + [ + "SD_CLK_EN", + 31, + 1 + ] + ] + }, + { + "name": "MISC", + "offset": "0x24", + "fields": [ + [ + "SPI_SWAP", + 31, + 1 + ] + ] + }, + { + "name": "VERSION", + "offset": "0x28", + "reset": "0x01506190", + "fields": [ + [ + "SD_DATE", + 0, + 28 + ] + ] + } + ] + }, + { + "name": "FLASH_ENCRYPTION", + "base": "0x3FF46000", + "registers": [ + { + "name": "BUFFER_0", + "offset": "0x00", + "fields": [ + [ + "BUFFER", + 0, + 8, + "w" + ] + ] + }, + { + "name": "BUFFER_1", + "offset": "0x04", + "fields": [ + [ + "BUFFER", + 0, + 8, + "w" + ] + ] + }, + { + "name": "BUFFER_2", + "offset": "0x08", + "fields": [ + [ + "BUFFER", + 0, + 8, + "w" + ] + ] + }, + { + "name": "BUFFER_3", + "offset": "0x0C", + "fields": [ + [ + "BUFFER", + 0, + 8, + "w" + ] + ] + }, + { + "name": "BUFFER_4", + "offset": "0x10", + "fields": [ + [ + "BUFFER", + 0, + 8, + "w" + ] + ] + }, + { + "name": "BUFFER_5", + "offset": "0x14", + "fields": [ + [ + "BUFFER", + 0, + 8, + "w" + ] + ] + }, + { + "name": "BUFFER_6", + "offset": "0x18", + "fields": [ + [ + "BUFFER", + 0, + 8, + "w" + ] + ] + }, + { + "name": "BUFFER_7", + "offset": "0x1C", + "fields": [ + [ + "BUFFER", + 0, + 8, + "w" + ] + ] + }, + { + "name": "START", + "offset": "0x20", + "fields": [ + [ + "FLASH_START", + 0, + 8, + "w" + ] + ] + }, + { + "name": "ADDRESS", + "offset": "0x24", + "fields": [ + [ + "ADDRESS", + 0, + 8, + "w" + ] + ] + }, + { + "name": "DONE", + "offset": "0x28", + "fields": [ + [ + "FLASH_DONE", + 0, + 1, + "r" + ] + ] + } + ] + }, + { + "name": "FRC_TIMER", + "base": "0x3FF47000", + "registers": [ + { + "name": "TIMER_LOAD", + "offset": "0x00", + "fields": [ + [ + "VALUE", + 0, + 8 + ] + ] + }, + { + "name": "TIMER_COUNT", + "offset": "0x04", + "fields": [ + [ + "TIMER_COUNT", + 0, + 8 + ] + ] + }, + { + "name": "TIMER_CTRL", + "offset": "0x08", + "fields": [ + [ + "TIMER_PRESCALER", + 1, + 8 + ] + ] + }, + { + "name": "TIMER_INT", + "offset": "0x0C", + "fields": [ + [ + "CLR", + 0, + 1 + ] + ] + }, + { + "name": "TIMER_ALARM", + "offset": "0x10", + "fields": [ + [ + "TIMER_ALARM", + 0, + 8 + ] + ] + } + ] + }, + { + "name": "RTC_CNTL", + "base": "0x3FF48000", + "interrupts": [ + "RTC_CORE" + ], + "registers": [ + { + "name": "OPTIONS0", + "offset": "0x00", + "reset": "0x1C492000", + "fields": [ + [ + "SW_STALL_APPCPU_C0", + 0, + 2 + ], + [ + "SW_STALL_PROCPU_C0", + 2, + 2 + ], + [ + "SW_APPCPU_RST", + 4, + 1, + "w" + ], + [ + "SW_PROCPU_RST", + 5, + 1, + "w" + ], + [ + "BB_I2C_FORCE_PD", + 6, + 1 + ], + [ + "BB_I2C_FORCE_PU", + 7, + 1 + ], + [ + "BBPLL_I2C_FORCE_PD", + 8, + 1 + ], + [ + "BBPLL_I2C_FORCE_PU", + 9, + 1 + ], + [ + "BBPLL_FORCE_PD", + 10, + 1 + ], + [ + "BBPLL_FORCE_PU", + 11, + 1 + ], + [ + "XTL_FORCE_PD", + 12, + 1 + ], + [ + "XTL_FORCE_PU", + 13, + 1 + ], + [ + "BIAS_SLEEP_FOLW_8M", + 14, + 1 + ], + [ + "BIAS_FORCE_SLEEP", + 15, + 1 + ], + [ + "BIAS_FORCE_NOSLEEP", + 16, + 1 + ], + [ + "BIAS_I2C_FOLW_8M", + 17, + 1 + ], + [ + "BIAS_I2C_FORCE_PD", + 18, + 1 + ], + [ + "BIAS_I2C_FORCE_PU", + 19, + 1 + ], + [ + "BIAS_CORE_FOLW_8M", + 20, + 1 + ], + [ + "BIAS_CORE_FORCE_PD", + 21, + 1 + ], + [ + "BIAS_CORE_FORCE_PU", + 22, + 1 + ], + [ + "XTL_FORCE_ISO", + 23, + 1 + ], + [ + "PLL_FORCE_ISO", + 24, + 1 + ], + [ + "ANALOG_FORCE_ISO", + 25, + 1 + ], + [ + "XTL_FORCE_NOISO", + 26, + 1 + ], + [ + "PLL_FORCE_NOISO", + 27, + 1 + ], + [ + "ANALOG_FORCE_NOISO", + 28, + 1 + ], + [ + "DG_WRAP_FORCE_RST", + 29, + 1 + ], + [ + "DG_WRAP_FORCE_NORST", + 30, + 1 + ], + [ + "SW_SYS_RST", + 31, + 1, + "w" + ] + ] + }, + { + "name": "SLP_TIMER0", + "offset": "0x04", + "fields": [ + [ + "SLP_VAL_LO", + 0, + 32 + ] + ] + }, + { + "name": "SLP_TIMER1", + "offset": "0x08", + "fields": [ + [ + "SLP_VAL_HI", + 0, + 16 + ], + [ + "MAIN_TIMER_ALARM_EN", + 16, + 1 + ] + ] + }, + { + "name": "TIME_UPDATE", + "offset": "0x0C", + "fields": [ + [ + "TIME_VALID", + 30, + 1, + "r" + ], + [ + "TIME_UPDATE", + 31, + 1, + "w" + ] + ] + }, + { + "name": "TIME0", + "offset": "0x10", + "fields": [ + [ + "TIME_LO", + 0, + 32, + "r" + ] + ] + }, + { + "name": "TIME1", + "offset": "0x14", + "fields": [ + [ + "TIME_HI", + 0, + 16, + "r" + ] + ] + }, + { + "name": "STATE0", + "offset": "0x18", + "reset": "0x00300000", + "fields": [ + [ + "TOUCH_WAKEUP_FORCE_EN", + 20, + 1 + ], + [ + "ULP_CP_WAKEUP_FORCE_EN", + 21, + 1 + ], + [ + "APB2RTC_BRIDGE_SEL", + 22, + 1 + ], + [ + "TOUCH_SLP_TIMER_EN", + 23, + 1 + ], + [ + "ULP_CP_SLP_TIMER_EN", + 24, + 1 + ], + [ + "SDIO_ACTIVE_IND", + 28, + 1, + "r" + ], + [ + "SLP_WAKEUP", + 29, + 1 + ], + [ + "SLP_REJECT", + 30, + 1 + ], + [ + "SLEEP_EN", + 31, + 1 + ] + ] + }, + { + "name": "TIMER1", + "offset": "0x1C", + "reset": "0x28140403", + "fields": [ + [ + "CPU_STALL_EN", + 0, + 1 + ], + [ + "CPU_STALL_WAIT", + 1, + 5 + ], + [ + "CK8M_WAIT", + 6, + 8 + ], + [ + "XTL_BUF_WAIT", + 14, + 10 + ], + [ + "PLL_BUF_WAIT", + 24, + 8 + ] + ] + }, + { + "name": "TIMER2", + "offset": "0x20", + "reset": "0x01080000", + "fields": [ + [ + "ULPCP_TOUCH_START_WAIT", + 15, + 9 + ], + [ + "MIN_TIME_CK8M_OFF", + 24, + 8 + ] + ] + }, + { + "name": "TIMER3", + "offset": "0x24", + "reset": "0x14160A08", + "fields": [ + [ + "WIFI_WAIT_TIMER", + 0, + 9 + ], + [ + "WIFI_POWERUP_TIMER", + 9, + 7 + ], + [ + "ROM_RAM_WAIT_TIMER", + 16, + 9 + ], + [ + "ROM_RAM_POWERUP_TIMER", + 25, + 7 + ] + ] + }, + { + "name": "TIMER4", + "offset": "0x28", + "reset": "0x10200A08", + "fields": [ + [ + "WAIT_TIMER", + 0, + 9 + ], + [ + "POWERUP_TIMER", + 9, + 7 + ], + [ + "DG_WRAP_WAIT_TIMER", + 16, + 9 + ], + [ + "DG_WRAP_POWERUP_TIMER", + 25, + 7 + ] + ] + }, + { + "name": "TIMER5", + "offset": "0x2C", + "reset": "0x12148001", + "fields": [ + [ + "ULP_CP_SUBTIMER_PREDIV", + 0, + 8 + ], + [ + "MIN_SLP_VAL", + 8, + 8 + ], + [ + "RTCMEM_WAIT_TIMER", + 16, + 9 + ], + [ + "RTCMEM_POWERUP_TIMER", + 25, + 7 + ] + ] + }, + { + "name": "ANA_CONF", + "offset": "0x30", + "reset": "0x00800000", + "fields": [ + [ + "PLLA_FORCE_PD", + 23, + 1 + ], + [ + "PLLA_FORCE_PU", + 24, + 1 + ], + [ + "BBPLL_CAL_SLP_START", + 25, + 1 + ], + [ + "PVTMON_PU", + 26, + 1 + ], + [ + "TXRF_I2C_PU", + 27, + 1 + ], + [ + "RFRX_PBUS_PU", + 28, + 1 + ], + [ + "CKGEN_I2C_PU", + 30, + 1 + ], + [ + "PLL_I2C_PU", + 31, + 1 + ] + ] + }, + { + "name": "RESET_STATE", + "offset": "0x34", + "reset": "0x00003000", + "fields": [ + [ + "RESET_CAUSE_PROCPU", + 0, + 6, + "r" + ], + [ + "RESET_CAUSE_APPCPU", + 6, + 6, + "r" + ], + [ + "APPCPU_STAT_VECTOR_SEL", + 12, + 1 + ], + [ + "PROCPU_STAT_VECTOR_SEL", + 13, + 1 + ] + ] + }, + { + "name": "WAKEUP_STATE", + "offset": "0x38", + "reset": "0x00006000", + "fields": [ + [ + "WAKEUP_CAUSE", + 0, + 11, + "r" + ], + [ + "WAKEUP_ENA", + 11, + 11 + ], + [ + "GPIO_WAKEUP_FILTER", + 22, + 1 + ] + ] + }, + { + "name": "INT_ENA", + "offset": "0x3C", + "fields": [ + [ + "SLP_WAKEUP_INT_ENA", + 0, + 1 + ], + [ + "SLP_REJECT_INT_ENA", + 1, + 1 + ], + [ + "SDIO_IDLE_INT_ENA", + 2, + 1 + ], + [ + "WDT_INT_ENA", + 3, + 1 + ], + [ + "TIME_VALID_INT_ENA", + 4, + 1 + ], + [ + "ULP_CP_INT_ENA", + 5, + 1 + ], + [ + "TOUCH_INT_ENA", + 6, + 1 + ], + [ + "BROWN_OUT_INT_ENA", + 7, + 1 + ], + [ + "MAIN_TIMER_INT_ENA", + 8, + 1 + ] + ] + }, + { + "name": "INT_RAW", + "offset": "0x40", + "fields": [ + [ + "SLP_WAKEUP_INT_RAW", + 0, + 1, + "r" + ], + [ + "SLP_REJECT_INT_RAW", + 1, + 1, + "r" + ], + [ + "SDIO_IDLE_INT_RAW", + 2, + 1, + "r" + ], + [ + "WDT_INT_RAW", + 3, + 1, + "r" + ], + [ + "TIME_VALID_INT_RAW", + 4, + 1, + "r" + ], + [ + "ULP_CP_INT_RAW", + 5, + 1, + "r" + ], + [ + "TOUCH_INT_RAW", + 6, + 1, + "r" + ], + [ + "BROWN_OUT_INT_RAW", + 7, + 1, + "r" + ], + [ + "MAIN_TIMER_INT_RAW", + 8, + 1, + "r" + ] + ] + }, + { + "name": "INT_ST", + "offset": "0x44", + "fields": [ + [ + "SLP_WAKEUP_INT_ST", + 0, + 1, + "r" + ], + [ + "SLP_REJECT_INT_ST", + 1, + 1, + "r" + ], + [ + "SDIO_IDLE_INT_ST", + 2, + 1, + "r" + ], + [ + "WDT_INT_ST", + 3, + 1, + "r" + ], + [ + "TIME_VALID_INT_ST", + 4, + 1, + "r" + ], + [ + "SAR_INT_ST", + 5, + 1, + "r" + ], + [ + "TOUCH_INT_ST", + 6, + 1, + "r" + ], + [ + "BROWN_OUT_INT_ST", + 7, + 1, + "r" + ], + [ + "MAIN_TIMER_INT_ST", + 8, + 1, + "r" + ] + ] + }, + { + "name": "INT_CLR", + "offset": "0x48", + "fields": [ + [ + "SLP_WAKEUP_INT_CLR", + 0, + 1, + "w" + ], + [ + "SLP_REJECT_INT_CLR", + 1, + 1, + "w" + ], + [ + "SDIO_IDLE_INT_CLR", + 2, + 1, + "w" + ], + [ + "WDT_INT_CLR", + 3, + 1, + "w" + ], + [ + "TIME_VALID_INT_CLR", + 4, + 1, + "w" + ], + [ + "SAR_INT_CLR", + 5, + 1, + "w" + ], + [ + "TOUCH_INT_CLR", + 6, + 1, + "w" + ], + [ + "BROWN_OUT_INT_CLR", + 7, + 1, + "w" + ], + [ + "MAIN_TIMER_INT_CLR", + 8, + 1, + "w" + ] + ] + }, + { + "name": "STORE0", + "offset": "0x4C", + "fields": [ + [ + "SCRATCH0", + 0, + 32 + ] + ] + }, + { + "name": "STORE1", + "offset": "0x50", + "fields": [ + [ + "SCRATCH1", + 0, + 32 + ] + ] + }, + { + "name": "STORE2", + "offset": "0x54", + "fields": [ + [ + "SCRATCH2", + 0, + 32 + ] + ] + }, + { + "name": "STORE3", + "offset": "0x58", + "fields": [ + [ + "SCRATCH3", + 0, + 32 + ] + ] + }, + { + "name": "EXT_XTL_CONF", + "offset": "0x5C", + "fields": [ + [ + "XTL_EXT_CTR_LV", + 30, + 1 + ], + [ + "XTL_EXT_CTR_EN", + 31, + 1 + ] + ] + }, + { + "name": "EXT_WAKEUP_CONF", + "offset": "0x60", + "fields": [ + [ + "EXT_WAKEUP0_LV", + 30, + 1 + ], + [ + "EXT_WAKEUP1_LV", + 31, + 1 + ] + ] + }, + { + "name": "SLP_REJECT_CONF", + "offset": "0x64", + "fields": [ + [ + "GPIO_REJECT_EN", + 24, + 1 + ], + [ + "SDIO_REJECT_EN", + 25, + 1 + ], + [ + "LIGHT_SLP_REJECT_EN", + 26, + 1 + ], + [ + "DEEP_SLP_REJECT_EN", + 27, + 1 + ], + [ + "REJECT_CAUSE", + 28, + 4, + "r" + ] + ] + }, + { + "name": "CPU_PERIOD_CONF", + "offset": "0x68", + "fields": [ + [ + "CPUSEL_CONF", + 29, + 1 + ], + [ + "CPUPERIOD_SEL", + 30, + 2 + ] + ] + }, + { + "name": "SDIO_ACT_CONF", + "offset": "0x6C", + "fields": [ + [ + "SDIO_ACT_DNUM", + 22, + 10 + ] + ] + }, + { + "name": "CLK_CONF", + "offset": "0x70", + "reset": "0x00002210", + "fields": [ + [ + "CK8M_DIV", + 4, + 2 + ], + [ + "ENB_CK8M", + 6, + 1 + ], + [ + "ENB_CK8M_DIV", + 7, + 1 + ], + [ + "DIG_XTAL32K_EN", + 8, + 1 + ], + [ + "DIG_CLK8M_D256_EN", + 9, + 1 + ], + [ + "DIG_CLK8M_EN", + 10, + 1 + ], + [ + "CK8M_DFREQ_FORCE", + 11, + 1 + ], + [ + "CK8M_DIV_SEL", + 12, + 3 + ], + [ + "XTAL_FORCE_NOGATING", + 15, + 1 + ], + [ + "CK8M_FORCE_NOGATING", + 16, + 1 + ], + [ + "CK8M_DFREQ", + 17, + 8 + ], + [ + "CK8M_FORCE_PD", + 25, + 1 + ], + [ + "CK8M_FORCE_PU", + 26, + 1 + ], + [ + "SOC_CLK_SEL", + 27, + 2 + ], + [ + "FAST_CLK_RTC_SEL", + 29, + 1 + ], + [ + "ANA_CLK_RTC_SEL", + 30, + 2 + ] + ] + }, + { + "name": "SDIO_CONF", + "offset": "0x74", + "reset": "0x02A00000", + "fields": [ + [ + "SDIO_PD_EN", + 21, + 1 + ], + [ + "SDIO_FORCE", + 22, + 1 + ], + [ + "SDIO_TIEH", + 23, + 1 + ], + [ + "REG1P8_READY", + 24, + 1, + "r" + ], + [ + "DREFL_SDIO", + 25, + 2 + ], + [ + "DREFM_SDIO", + 27, + 2 + ], + [ + "DREFH_SDIO", + 29, + 2 + ], + [ + "XPD_SDIO", + 31, + 1 + ] + ] + }, + { + "name": "BIAS_CONF", + "offset": "0x78", + "fields": [ + [ + "DBG_ATTEN", + 24, + 2 + ], + [ + "ENB_SCK_XTAL", + 26, + 1 + ], + [ + "INC_HEARTBEAT_REFRESH", + 27, + 1 + ], + [ + "DEC_HEARTBEAT_PERIOD", + 28, + 1 + ], + [ + "INC_HEARTBEAT_PERIOD", + 29, + 1 + ], + [ + "DEC_HEARTBEAT_WIDTH", + 30, + 1 + ], + [ + "RST_BIAS_I2C", + 31, + 1 + ] + ] + }, + { + "name": "REG", + "offset": "0x7C", + "reset": "0x29002400", + "fields": [ + [ + "SCK_DCAP_FORCE", + 7, + 1 + ], + [ + "DIG_DBIAS_SLP", + 8, + 3 + ], + [ + "DIG_DBIAS_WAK", + 11, + 3 + ], + [ + "SCK_DCAP", + 14, + 8 + ], + [ + "DBIAS_SLP", + 22, + 3 + ], + [ + "DBIAS_WAK", + 25, + 3 + ], + [ + "DBOOST_FORCE_PD", + 28, + 1 + ], + [ + "DBOOST_FORCE_PU", + 29, + 1 + ], + [ + "FORCE_PD", + 30, + 1 + ], + [ + "FORCE_PU", + 31, + 1 + ] + ] + }, + { + "name": "PWC", + "offset": "0x80", + "reset": "0x00012925", + "fields": [ + [ + "FASTMEM_FORCE_NOISO", + 0, + 1 + ], + [ + "FASTMEM_FORCE_ISO", + 1, + 1 + ], + [ + "SLOWMEM_FORCE_NOISO", + 2, + 1 + ], + [ + "SLOWMEM_FORCE_ISO", + 3, + 1 + ], + [ + "FORCE_ISO", + 4, + 1 + ], + [ + "FORCE_NOISO", + 5, + 1 + ], + [ + "FASTMEM_FOLW_CPU", + 6, + 1 + ], + [ + "FASTMEM_FORCE_LPD", + 7, + 1 + ], + [ + "FASTMEM_FORCE_LPU", + 8, + 1 + ], + [ + "SLOWMEM_FOLW_CPU", + 9, + 1 + ], + [ + "SLOWMEM_FORCE_LPD", + 10, + 1 + ], + [ + "SLOWMEM_FORCE_LPU", + 11, + 1 + ], + [ + "FASTMEM_FORCE_PD", + 12, + 1 + ], + [ + "FASTMEM_FORCE_PU", + 13, + 1 + ], + [ + "FASTMEM_PD_EN", + 14, + 1 + ], + [ + "SLOWMEM_FORCE_PD", + 15, + 1 + ], + [ + "SLOWMEM_FORCE_PU", + 16, + 1 + ], + [ + "SLOWMEM_PD_EN", + 17, + 1 + ], + [ + "FORCE_PD", + 18, + 1 + ], + [ + "FORCE_PU", + 19, + 1 + ], + [ + "PD_EN", + 20, + 1 + ] + ] + }, + { + "name": "DIG_PWC", + "offset": "0x84", + "reset": "0x00155550", + "fields": [ + [ + "LSLP_MEM_FORCE_PD", + 3, + 1 + ], + [ + "LSLP_MEM_FORCE_PU", + 4, + 1 + ], + [ + "ROM0_FORCE_PD", + 5, + 1 + ], + [ + "ROM0_FORCE_PU", + 6, + 1 + ], + [ + "INTER_RAM0_FORCE_PD", + 7, + 1 + ], + [ + "INTER_RAM0_FORCE_PU", + 8, + 1 + ], + [ + "INTER_RAM1_FORCE_PD", + 9, + 1 + ], + [ + "INTER_RAM1_FORCE_PU", + 10, + 1 + ], + [ + "INTER_RAM2_FORCE_PD", + 11, + 1 + ], + [ + "INTER_RAM2_FORCE_PU", + 12, + 1 + ], + [ + "INTER_RAM3_FORCE_PD", + 13, + 1 + ], + [ + "INTER_RAM3_FORCE_PU", + 14, + 1 + ], + [ + "INTER_RAM4_FORCE_PD", + 15, + 1 + ], + [ + "INTER_RAM4_FORCE_PU", + 16, + 1 + ], + [ + "WIFI_FORCE_PD", + 17, + 1 + ], + [ + "WIFI_FORCE_PU", + 18, + 1 + ], + [ + "DG_WRAP_FORCE_PD", + 19, + 1 + ], + [ + "DG_WRAP_FORCE_PU", + 20, + 1 + ], + [ + "ROM0_PD_EN", + 24, + 1 + ], + [ + "INTER_RAM0_PD_EN", + 25, + 1 + ], + [ + "INTER_RAM1_PD_EN", + 26, + 1 + ], + [ + "INTER_RAM2_PD_EN", + 27, + 1 + ], + [ + "INTER_RAM3_PD_EN", + 28, + 1 + ], + [ + "INTER_RAM4_PD_EN", + 29, + 1 + ], + [ + "WIFI_PD_EN", + 30, + 1 + ], + [ + "DG_WRAP_PD_EN", + 31, + 1 + ] + ] + }, + { + "name": "DIG_ISO", + "offset": "0x88", + "reset": "0xAAAA5000", + "fields": [ + [ + "FORCE_OFF", + 7, + 1 + ], + [ + "FORCE_ON", + 8, + 1 + ], + [ + "DG_PAD_AUTOHOLD", + 9, + 1, + "r" + ], + [ + "CLR_DG_PAD_AUTOHOLD", + 10, + 1, + "w" + ], + [ + "DG_PAD_AUTOHOLD_EN", + 11, + 1 + ], + [ + "DG_PAD_FORCE_NOISO", + 12, + 1 + ], + [ + "DG_PAD_FORCE_ISO", + 13, + 1 + ], + [ + "DG_PAD_FORCE_UNHOLD", + 14, + 1 + ], + [ + "DG_PAD_FORCE_HOLD", + 15, + 1 + ], + [ + "ROM0_FORCE_ISO", + 16, + 1 + ], + [ + "ROM0_FORCE_NOISO", + 17, + 1 + ], + [ + "INTER_RAM0_FORCE_ISO", + 18, + 1 + ], + [ + "INTER_RAM0_FORCE_NOISO", + 19, + 1 + ], + [ + "INTER_RAM1_FORCE_ISO", + 20, + 1 + ], + [ + "INTER_RAM1_FORCE_NOISO", + 21, + 1 + ], + [ + "INTER_RAM2_FORCE_ISO", + 22, + 1 + ], + [ + "INTER_RAM2_FORCE_NOISO", + 23, + 1 + ], + [ + "INTER_RAM3_FORCE_ISO", + 24, + 1 + ], + [ + "INTER_RAM3_FORCE_NOISO", + 25, + 1 + ], + [ + "INTER_RAM4_FORCE_ISO", + 26, + 1 + ], + [ + "INTER_RAM4_FORCE_NOISO", + 27, + 1 + ], + [ + "WIFI_FORCE_ISO", + 28, + 1 + ], + [ + "WIFI_FORCE_NOISO", + 29, + 1 + ], + [ + "DG_WRAP_FORCE_ISO", + 30, + 1 + ], + [ + "DG_WRAP_FORCE_NOISO", + 31, + 1 + ] + ] + }, + { + "name": "WDTCONFIG0", + "offset": "0x8C", + "reset": "0x00004C80", + "fields": [ + [ + "WDT_PAUSE_IN_SLP", + 7, + 1 + ], + [ + "WDT_APPCPU_RESET_EN", + 8, + 1 + ], + [ + "WDT_PROCPU_RESET_EN", + 9, + 1 + ], + [ + "WDT_FLASHBOOT_MOD_EN", + 10, + 1 + ], + [ + "WDT_SYS_RESET_LENGTH", + 11, + 3 + ], + [ + "WDT_CPU_RESET_LENGTH", + 14, + 3 + ], + [ + "WDT_LEVEL_INT_EN", + 17, + 1 + ], + [ + "WDT_EDGE_INT_EN", + 18, + 1 + ], + [ + "WDT_STG3", + 19, + 3 + ], + [ + "WDT_STG2", + 22, + 3 + ], + [ + "WDT_STG1", + 25, + 3 + ], + [ + "WDT_STG0", + 28, + 3 + ], + [ + "WDT_EN", + 31, + 1 + ] + ] + }, + { + "name": "WDTCONFIG1", + "offset": "0x90", + "reset": "0x0001F400", + "fields": [ + [ + "WDT_STG0_HOLD", + 0, + 32 + ] + ] + }, + { + "name": "WDTCONFIG2", + "offset": "0x94", + "reset": "0x00013880", + "fields": [ + [ + "WDT_STG1_HOLD", + 0, + 32 + ] + ] + }, + { + "name": "WDTCONFIG3", + "offset": "0x98", + "reset": "0x00000FFF", + "fields": [ + [ + "WDT_STG2_HOLD", + 0, + 32 + ] + ] + }, + { + "name": "WDTCONFIG4", + "offset": "0x9C", + "reset": "0x00000FFF", + "fields": [ + [ + "WDT_STG3_HOLD", + 0, + 32 + ] + ] + }, + { + "name": "WDTFEED", + "offset": "0xA0", + "fields": [ + [ + "WDT_FEED", + 31, + 1, + "w" + ] + ] + }, + { + "name": "WDTWPROTECT", + "offset": "0xA4", + "reset": "0x50D83AA1", + "fields": [ + [ + "WDT_WKEY", + 0, + 32 + ] + ] + }, + { + "name": "TEST_MUX", + "offset": "0xA8", + "fields": [ + [ + "ENT_RTC", + 29, + 1 + ], + [ + "DTEST_RTC", + 30, + 2 + ] + ] + }, + { + "name": "SW_CPU_STALL", + "offset": "0xAC", + "fields": [ + [ + "SW_STALL_APPCPU_C1", + 20, + 6 + ], + [ + "SW_STALL_PROCPU_C1", + 26, + 6 + ] + ] + }, + { + "name": "STORE4", + "offset": "0xB0", + "fields": [ + [ + "SCRATCH4", + 0, + 32 + ] + ] + }, + { + "name": "STORE5", + "offset": "0xB4", + "fields": [ + [ + "SCRATCH5", + 0, + 32 + ] + ] + }, + { + "name": "STORE6", + "offset": "0xB8", + "fields": [ + [ + "SCRATCH6", + 0, + 32 + ] + ] + }, + { + "name": "STORE7", + "offset": "0xBC", + "fields": [ + [ + "SCRATCH7", + 0, + 32 + ] + ] + }, + { + "name": "LOW_POWER_ST", + "offset": "0xC0", + "fields": [ + [ + "LOW_POWER_DIAG0", + 0, + 32, + "r" + ], + [ + "RDY_FOR_WAKEUP", + 19, + 1, + "r" + ] + ] + }, + { + "name": "DIAG1", + "offset": "0xC4", + "fields": [ + [ + "LOW_POWER_DIAG1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "HOLD_FORCE", + "offset": "0xC8", + "fields": [ + [ + "ADC1_HOLD_FORCE", + 0, + 1 + ], + [ + "ADC2_HOLD_FORCE", + 1, + 1 + ], + [ + "PDAC1_HOLD_FORCE", + 2, + 1 + ], + [ + "PDAC2_HOLD_FORCE", + 3, + 1 + ], + [ + "SENSE1_HOLD_FORCE", + 4, + 1 + ], + [ + "SENSE2_HOLD_FORCE", + 5, + 1 + ], + [ + "SENSE3_HOLD_FORCE", + 6, + 1 + ], + [ + "SENSE4_HOLD_FORCE", + 7, + 1 + ], + [ + "TOUCH_PAD0_HOLD_FORCE", + 8, + 1 + ], + [ + "TOUCH_PAD1_HOLD_FORCE", + 9, + 1 + ], + [ + "TOUCH_PAD2_HOLD_FORCE", + 10, + 1 + ], + [ + "TOUCH_PAD3_HOLD_FORCE", + 11, + 1 + ], + [ + "TOUCH_PAD4_HOLD_FORCE", + 12, + 1 + ], + [ + "TOUCH_PAD5_HOLD_FORCE", + 13, + 1 + ], + [ + "TOUCH_PAD6_HOLD_FORCE", + 14, + 1 + ], + [ + "TOUCH_PAD7_HOLD_FORCE", + 15, + 1 + ], + [ + "X32P_HOLD_FORCE", + 16, + 1 + ], + [ + "X32N_HOLD_FORCE", + 17, + 1 + ] + ] + }, + { + "name": "EXT_WAKEUP1", + "offset": "0xCC", + "fields": [ + [ + "SEL", + 0, + 18 + ], + [ + "STATUS_CLR", + 18, + 1, + "w" + ] + ] + }, + { + "name": "EXT_WAKEUP1_STATUS", + "offset": "0xD0", + "fields": [ + [ + "EXT_WAKEUP1_STATUS", + 0, + 18, + "r" + ] + ] + }, + { + "name": "BROWN_OUT", + "offset": "0xD4", + "reset": "0x13FF0000", + "fields": [ + [ + "RTC_MEM_PID_CONF", + 0, + 8 + ], + [ + "RTC_MEM_CRC_START", + 8, + 1 + ], + [ + "RTC_MEM_CRC_ADDR", + 9, + 11 + ], + [ + "CLOSE_FLASH_ENA", + 14, + 1 + ], + [ + "PD_RF_ENA", + 15, + 1 + ], + [ + "RST_WAIT", + 16, + 10 + ], + [ + "RTC_MEM_CRC_LEN", + 20, + 11 + ], + [ + "RST_ENA", + 26, + 1 + ], + [ + "DBROWN_OUT_THRES", + 27, + 3 + ], + [ + "ENA", + 30, + 1 + ], + [ + "DET", + 31, + 1, + "r" + ], + [ + "RTC_MEM_CRC_FINISH", + 31, + 1 + ] + ] + }, + { + "name": "DATE", + "offset": "0x13C", + "reset": "0x01604280", + "fields": [ + [ + "CNTL_DATE", + 0, + 28 + ] + ] + } + ] + }, + { + "name": "RTC_IO", + "base": "0x3FF48400", + "registers": [ + { + "name": "OUT", + "offset": "0x00", + "fields": [ + [ + "DATA", + 14, + 18 + ] + ] + }, + { + "name": "OUT_W1TS", + "offset": "0x04", + "fields": [ + [ + "OUT_DATA_W1TS", + 14, + 18, + "w" + ] + ] + }, + { + "name": "OUT_W1TC", + "offset": "0x08", + "fields": [ + [ + "OUT_DATA_W1TC", + 14, + 18, + "w" + ] + ] + }, + { + "name": "ENABLE", + "offset": "0x0C", + "fields": [ + [ + "ENABLE", + 14, + 18 + ] + ] + }, + { + "name": "ENABLE_W1TS", + "offset": "0x10", + "fields": [ + [ + "ENABLE_W1TS", + 14, + 18, + "w" + ] + ] + }, + { + "name": "ENABLE_W1TC", + "offset": "0x14", + "fields": [ + [ + "ENABLE_W1TC", + 14, + 18, + "w" + ] + ] + }, + { + "name": "STATUS", + "offset": "0x18", + "fields": [ + [ + "INT", + 14, + 18 + ] + ] + }, + { + "name": "STATUS_W1TS", + "offset": "0x1C", + "fields": [ + [ + "STATUS_INT_W1TS", + 14, + 18, + "w" + ] + ] + }, + { + "name": "STATUS_W1TC", + "offset": "0x20", + "fields": [ + [ + "STATUS_INT_W1TC", + 14, + 18, + "w" + ] + ] + }, + { + "name": "IN", + "offset": "0x24", + "fields": [ + [ + "NEXT", + 14, + 18, + "r" + ] + ] + }, + { + "name": "PIN0", + "offset": "0x28", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN1", + "offset": "0x2C", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN2", + "offset": "0x30", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN3", + "offset": "0x34", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN4", + "offset": "0x38", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN5", + "offset": "0x3C", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN6", + "offset": "0x40", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN7", + "offset": "0x44", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN8", + "offset": "0x48", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN9", + "offset": "0x4C", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN10", + "offset": "0x50", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN11", + "offset": "0x54", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN12", + "offset": "0x58", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN13", + "offset": "0x5C", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN14", + "offset": "0x60", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN15", + "offset": "0x64", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN16", + "offset": "0x68", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "PIN17", + "offset": "0x6C", + "fields": [ + [ + "PAD_DRIVER", + 2, + 1 + ], + [ + "INT_TYPE", + 7, + 3 + ], + [ + "WAKEUP_ENABLE", + 10, + 1 + ] + ] + }, + { + "name": "RTC_DEBUG_SEL", + "offset": "0x70", + "fields": [ + [ + "DEBUG_SEL0", + 0, + 5 + ], + [ + "DEBUG_SEL1", + 5, + 5 + ], + [ + "DEBUG_SEL2", + 10, + 5 + ], + [ + "DEBUG_SEL3", + 15, + 5 + ], + [ + "DEBUG_SEL4", + 20, + 5 + ], + [ + "DEBUG_12M_NO_GATING", + 25, + 1 + ] + ] + }, + { + "name": "DIG_PAD_HOLD", + "offset": "0x74", + "fields": [ + [ + "DIG_PAD_HOLD", + 0, + 32 + ] + ] + }, + { + "name": "HALL_SENS", + "offset": "0x78", + "fields": [ + [ + "HALL_PHASE", + 30, + 1 + ], + [ + "XPD_HALL", + 31, + 1 + ] + ] + }, + { + "name": "SENSOR_PADS", + "offset": "0x7C", + "fields": [ + [ + "SENSE4_FUN_IE", + 4, + 1 + ], + [ + "SENSE4_SLP_IE", + 5, + 1 + ], + [ + "SENSE4_SLP_SEL", + 6, + 1 + ], + [ + "SENSE4_FUN_SEL", + 7, + 2 + ], + [ + "SENSE3_FUN_IE", + 9, + 1 + ], + [ + "SENSE3_SLP_IE", + 10, + 1 + ], + [ + "SENSE3_SLP_SEL", + 11, + 1 + ], + [ + "SENSE3_FUN_SEL", + 12, + 2 + ], + [ + "SENSE2_FUN_IE", + 14, + 1 + ], + [ + "SENSE2_SLP_IE", + 15, + 1 + ], + [ + "SENSE2_SLP_SEL", + 16, + 1 + ], + [ + "SENSE2_FUN_SEL", + 17, + 2 + ], + [ + "SENSE1_FUN_IE", + 19, + 1 + ], + [ + "SENSE1_SLP_IE", + 20, + 1 + ], + [ + "SENSE1_SLP_SEL", + 21, + 1 + ], + [ + "SENSE1_FUN_SEL", + 22, + 2 + ], + [ + "SENSE4_MUX_SEL", + 24, + 1 + ], + [ + "SENSE3_MUX_SEL", + 25, + 1 + ], + [ + "SENSE2_MUX_SEL", + 26, + 1 + ], + [ + "SENSE1_MUX_SEL", + 27, + 1 + ], + [ + "SENSE4_HOLD", + 28, + 1 + ], + [ + "SENSE3_HOLD", + 29, + 1 + ], + [ + "SENSE2_HOLD", + 30, + 1 + ], + [ + "SENSE1_HOLD", + 31, + 1 + ] + ] + }, + { + "name": "ADC_PAD", + "offset": "0x80", + "fields": [ + [ + "ADC2_FUN_IE", + 18, + 1 + ], + [ + "ADC2_SLP_IE", + 19, + 1 + ], + [ + "ADC2_SLP_SEL", + 20, + 1 + ], + [ + "ADC2_FUN_SEL", + 21, + 2 + ], + [ + "ADC1_FUN_IE", + 23, + 1 + ], + [ + "ADC1_SLP_IE", + 24, + 1 + ], + [ + "ADC1_SLP_SEL", + 25, + 1 + ], + [ + "ADC1_FUN_SEL", + 26, + 2 + ], + [ + "ADC2_MUX_SEL", + 28, + 1 + ], + [ + "ADC1_MUX_SEL", + 29, + 1 + ], + [ + "ADC2_HOLD", + 30, + 1 + ], + [ + "ADC1_HOLD", + 31, + 1 + ] + ] + }, + { + "name": "PAD_DAC1", + "offset": "0x84", + "reset": "0x80000000", + "fields": [ + [ + "PDAC1_DAC_XPD_FORCE", + 10, + 1 + ], + [ + "PDAC1_FUN_IE", + 11, + 1 + ], + [ + "PDAC1_SLP_OE", + 12, + 1 + ], + [ + "PDAC1_SLP_IE", + 13, + 1 + ], + [ + "PDAC1_SLP_SEL", + 14, + 1 + ], + [ + "PDAC1_FUN_SEL", + 15, + 2 + ], + [ + "PDAC1_MUX_SEL", + 17, + 1 + ], + [ + "PDAC1_XPD_DAC", + 18, + 1 + ], + [ + "PDAC1_DAC", + 19, + 8 + ], + [ + "PDAC1_RUE", + 27, + 1 + ], + [ + "PDAC1_RDE", + 28, + 1 + ], + [ + "PDAC1_HOLD", + 29, + 1 + ], + [ + "PDAC1_DRV", + 30, + 2 + ] + ] + }, + { + "name": "PAD_DAC2", + "offset": "0x88", + "reset": "0x80000000", + "fields": [ + [ + "PDAC2_DAC_XPD_FORCE", + 10, + 1 + ], + [ + "PDAC2_FUN_IE", + 11, + 1 + ], + [ + "PDAC2_SLP_OE", + 12, + 1 + ], + [ + "PDAC2_SLP_IE", + 13, + 1 + ], + [ + "PDAC2_SLP_SEL", + 14, + 1 + ], + [ + "PDAC2_FUN_SEL", + 15, + 2 + ], + [ + "PDAC2_MUX_SEL", + 17, + 1 + ], + [ + "PDAC2_XPD_DAC", + 18, + 1 + ], + [ + "PDAC2_DAC", + 19, + 8 + ], + [ + "PDAC2_RUE", + 27, + 1 + ], + [ + "PDAC2_RDE", + 28, + 1 + ], + [ + "PDAC2_HOLD", + 29, + 1 + ], + [ + "PDAC2_DRV", + 30, + 2 + ] + ] + }, + { + "name": "XTAL_32K_PAD", + "offset": "0x8C", + "reset": "0x84100010", + "fields": [ + [ + "DBIAS_XTAL_32K", + 1, + 2 + ], + [ + "DRES_XTAL_32K", + 3, + 2 + ], + [ + "X32P_FUN_IE", + 5, + 1 + ], + [ + "X32P_SLP_OE", + 6, + 1 + ], + [ + "X32P_SLP_IE", + 7, + 1 + ], + [ + "X32P_SLP_SEL", + 8, + 1 + ], + [ + "X32P_FUN_SEL", + 9, + 2 + ], + [ + "X32N_FUN_IE", + 11, + 1 + ], + [ + "X32N_SLP_OE", + 12, + 1 + ], + [ + "X32N_SLP_IE", + 13, + 1 + ], + [ + "X32N_SLP_SEL", + 14, + 1 + ], + [ + "X32N_FUN_SEL", + 15, + 2 + ], + [ + "X32P_MUX_SEL", + 17, + 1 + ], + [ + "X32N_MUX_SEL", + 18, + 1 + ], + [ + "XPD_XTAL_32K", + 19, + 1 + ], + [ + "DAC_XTAL_32K", + 20, + 2 + ], + [ + "X32P_RUE", + 22, + 1 + ], + [ + "X32P_RDE", + 23, + 1 + ], + [ + "X32P_HOLD", + 24, + 1 + ], + [ + "X32P_DRV", + 25, + 2 + ], + [ + "X32N_RUE", + 27, + 1 + ], + [ + "X32N_RDE", + 28, + 1 + ], + [ + "X32N_HOLD", + 29, + 1 + ], + [ + "X32N_DRV", + 30, + 2 + ] + ] + }, + { + "name": "TOUCH_CFG", + "offset": "0x90", + "reset": "0x66000000", + "fields": [ + [ + "TOUCH_DCUR", + 23, + 2 + ], + [ + "TOUCH_DRANGE", + 25, + 2 + ], + [ + "TOUCH_DREFL", + 27, + 2 + ], + [ + "TOUCH_DREFH", + 29, + 2 + ], + [ + "TOUCH_XPD_BIAS", + 31, + 1 + ] + ] + }, + { + "name": "TOUCH_PAD0", + "offset": "0x94", + "reset": "0x52000000", + "fields": [ + [ + "TO_GPIO", + 12, + 1 + ], + [ + "FUN_IE", + 13, + 1 + ], + [ + "SLP_OE", + 14, + 1 + ], + [ + "SLP_IE", + 15, + 1 + ], + [ + "SLP_SEL", + 16, + 1 + ], + [ + "FUN_SEL", + 17, + 2 + ], + [ + "MUX_SEL", + 19, + 1 + ], + [ + "XPD", + 20, + 1 + ], + [ + "TIE_OPT", + 21, + 1 + ], + [ + "START", + 22, + 1 + ], + [ + "DAC", + 23, + 3 + ], + [ + "RUE", + 27, + 1 + ], + [ + "RDE", + 28, + 1 + ], + [ + "DRV", + 29, + 2 + ], + [ + "HOLD", + 31, + 1 + ] + ] + }, + { + "name": "TOUCH_PAD1", + "offset": "0x98", + "reset": "0x4A000000", + "fields": [ + [ + "TO_GPIO", + 12, + 1 + ], + [ + "FUN_IE", + 13, + 1 + ], + [ + "SLP_OE", + 14, + 1 + ], + [ + "SLP_IE", + 15, + 1 + ], + [ + "SLP_SEL", + 16, + 1 + ], + [ + "FUN_SEL", + 17, + 2 + ], + [ + "MUX_SEL", + 19, + 1 + ], + [ + "XPD", + 20, + 1 + ], + [ + "TIE_OPT", + 21, + 1 + ], + [ + "START", + 22, + 1 + ], + [ + "DAC", + 23, + 3 + ], + [ + "RUE", + 27, + 1 + ], + [ + "RDE", + 28, + 1 + ], + [ + "DRV", + 29, + 2 + ], + [ + "HOLD", + 31, + 1 + ] + ] + }, + { + "name": "TOUCH_PAD2", + "offset": "0x9C", + "reset": "0x52000000", + "fields": [ + [ + "TO_GPIO", + 12, + 1 + ], + [ + "FUN_IE", + 13, + 1 + ], + [ + "SLP_OE", + 14, + 1 + ], + [ + "SLP_IE", + 15, + 1 + ], + [ + "SLP_SEL", + 16, + 1 + ], + [ + "FUN_SEL", + 17, + 2 + ], + [ + "MUX_SEL", + 19, + 1 + ], + [ + "XPD", + 20, + 1 + ], + [ + "TIE_OPT", + 21, + 1 + ], + [ + "START", + 22, + 1 + ], + [ + "DAC", + 23, + 3 + ], + [ + "RUE", + 27, + 1 + ], + [ + "RDE", + 28, + 1 + ], + [ + "DRV", + 29, + 2 + ], + [ + "HOLD", + 31, + 1 + ] + ] + }, + { + "name": "TOUCH_PAD3", + "offset": "0xA0", + "reset": "0x4A000000", + "fields": [ + [ + "TO_GPIO", + 12, + 1 + ], + [ + "FUN_IE", + 13, + 1 + ], + [ + "SLP_OE", + 14, + 1 + ], + [ + "SLP_IE", + 15, + 1 + ], + [ + "SLP_SEL", + 16, + 1 + ], + [ + "FUN_SEL", + 17, + 2 + ], + [ + "MUX_SEL", + 19, + 1 + ], + [ + "XPD", + 20, + 1 + ], + [ + "TIE_OPT", + 21, + 1 + ], + [ + "START", + 22, + 1 + ], + [ + "DAC", + 23, + 3 + ], + [ + "RUE", + 27, + 1 + ], + [ + "RDE", + 28, + 1 + ], + [ + "DRV", + 29, + 2 + ], + [ + "HOLD", + 31, + 1 + ] + ] + }, + { + "name": "TOUCH_PAD4", + "offset": "0xA4", + "reset": "0x52000000", + "fields": [ + [ + "TO_GPIO", + 12, + 1 + ], + [ + "FUN_IE", + 13, + 1 + ], + [ + "SLP_OE", + 14, + 1 + ], + [ + "SLP_IE", + 15, + 1 + ], + [ + "SLP_SEL", + 16, + 1 + ], + [ + "FUN_SEL", + 17, + 2 + ], + [ + "MUX_SEL", + 19, + 1 + ], + [ + "XPD", + 20, + 1 + ], + [ + "TIE_OPT", + 21, + 1 + ], + [ + "START", + 22, + 1 + ], + [ + "DAC", + 23, + 3 + ], + [ + "RUE", + 27, + 1 + ], + [ + "RDE", + 28, + 1 + ], + [ + "DRV", + 29, + 2 + ], + [ + "HOLD", + 31, + 1 + ] + ] + }, + { + "name": "TOUCH_PAD5", + "offset": "0xA8", + "reset": "0x52000000", + "fields": [ + [ + "TO_GPIO", + 12, + 1 + ], + [ + "FUN_IE", + 13, + 1 + ], + [ + "SLP_OE", + 14, + 1 + ], + [ + "SLP_IE", + 15, + 1 + ], + [ + "SLP_SEL", + 16, + 1 + ], + [ + "FUN_SEL", + 17, + 2 + ], + [ + "MUX_SEL", + 19, + 1 + ], + [ + "XPD", + 20, + 1 + ], + [ + "TIE_OPT", + 21, + 1 + ], + [ + "START", + 22, + 1 + ], + [ + "DAC", + 23, + 3 + ], + [ + "RUE", + 27, + 1 + ], + [ + "RDE", + 28, + 1 + ], + [ + "DRV", + 29, + 2 + ], + [ + "HOLD", + 31, + 1 + ] + ] + }, + { + "name": "TOUCH_PAD6", + "offset": "0xAC", + "reset": "0x4A000000", + "fields": [ + [ + "TO_GPIO", + 12, + 1 + ], + [ + "FUN_IE", + 13, + 1 + ], + [ + "SLP_OE", + 14, + 1 + ], + [ + "SLP_IE", + 15, + 1 + ], + [ + "SLP_SEL", + 16, + 1 + ], + [ + "FUN_SEL", + 17, + 2 + ], + [ + "MUX_SEL", + 19, + 1 + ], + [ + "XPD", + 20, + 1 + ], + [ + "TIE_OPT", + 21, + 1 + ], + [ + "START", + 22, + 1 + ], + [ + "DAC", + 23, + 3 + ], + [ + "RUE", + 27, + 1 + ], + [ + "RDE", + 28, + 1 + ], + [ + "DRV", + 29, + 2 + ], + [ + "HOLD", + 31, + 1 + ] + ] + }, + { + "name": "TOUCH_PAD7", + "offset": "0xB0", + "reset": "0x42000000", + "fields": [ + [ + "TO_GPIO", + 12, + 1 + ], + [ + "FUN_IE", + 13, + 1 + ], + [ + "SLP_OE", + 14, + 1 + ], + [ + "SLP_IE", + 15, + 1 + ], + [ + "SLP_SEL", + 16, + 1 + ], + [ + "FUN_SEL", + 17, + 2 + ], + [ + "MUX_SEL", + 19, + 1 + ], + [ + "XPD", + 20, + 1 + ], + [ + "TIE_OPT", + 21, + 1 + ], + [ + "START", + 22, + 1 + ], + [ + "DAC", + 23, + 3 + ], + [ + "RUE", + 27, + 1 + ], + [ + "RDE", + 28, + 1 + ], + [ + "DRV", + 29, + 2 + ], + [ + "HOLD", + 31, + 1 + ] + ] + }, + { + "name": "TOUCH_PAD8", + "offset": "0xB4", + "reset": "0x02000000", + "fields": [ + [ + "TO_GPIO", + 19, + 1 + ], + [ + "XPD", + 20, + 1 + ], + [ + "TIE_OPT", + 21, + 1 + ], + [ + "START", + 22, + 1 + ], + [ + "DAC", + 23, + 3 + ] + ] + }, + { + "name": "TOUCH_PAD9", + "offset": "0xB8", + "reset": "0x02000000", + "fields": [ + [ + "TO_GPIO", + 19, + 1 + ], + [ + "XPD", + 20, + 1 + ], + [ + "TIE_OPT", + 21, + 1 + ], + [ + "START", + 22, + 1 + ], + [ + "DAC", + 23, + 3 + ] + ] + }, + { + "name": "EXT_WAKEUP0", + "offset": "0xBC", + "fields": [ + [ + "SEL", + 27, + 5 + ] + ] + }, + { + "name": "XTL_EXT_CTR", + "offset": "0xC0", + "fields": [ + [ + "SEL", + 27, + 5 + ] + ] + }, + { + "name": "SAR_I2C_IO", + "offset": "0xC4", + "fields": [ + [ + "SAR_DEBUG_BIT_SEL", + 23, + 5 + ], + [ + "SAR_I2C_SCL_SEL", + 28, + 2 + ], + [ + "SAR_I2C_SDA_SEL", + 30, + 2 + ] + ] + }, + { + "name": "DATE", + "offset": "0xC8", + "reset": "0x01603160", + "fields": [ + [ + "IO_DATE", + 0, + 28 + ] + ] + } + ] + }, + { + "name": "SENS", + "base": "0x3FF48800", + "registers": [ + { + "name": "SAR_READ_CTRL", + "offset": "0x00", + "reset": "0x00070902", + "fields": [ + [ + "SAR1_CLK_DIV", + 0, + 8 + ], + [ + "SAR1_SAMPLE_CYCLE", + 8, + 8 + ], + [ + "SAR1_SAMPLE_BIT", + 16, + 2 + ], + [ + "SAR1_CLK_GATED", + 18, + 1 + ], + [ + "SAR1_SAMPLE_NUM", + 19, + 8 + ], + [ + "SAR1_DIG_FORCE", + 27, + 1 + ], + [ + "SAR1_DATA_INV", + 28, + 1 + ] + ] + }, + { + "name": "SAR_READ_STATUS1", + "offset": "0x04", + "fields": [ + [ + "SAR1_READER_STATUS", + 0, + 32, + "r" + ] + ] + }, + { + "name": "SAR_MEAS_WAIT1", + "offset": "0x08", + "reset": "0x000A000A", + "fields": [ + [ + "SAR_AMP_WAIT1", + 0, + 16 + ], + [ + "SAR_AMP_WAIT2", + 16, + 16 + ] + ] + }, + { + "name": "SAR_MEAS_WAIT2", + "offset": "0x0C", + "reset": "0x0020000A", + "fields": [ + [ + "FORCE_XPD_SAR_SW", + 0, + 1 + ], + [ + "SAR_AMP_WAIT3", + 0, + 16 + ], + [ + "FORCE_XPD_AMP", + 16, + 2 + ], + [ + "FORCE_XPD_SAR", + 18, + 2 + ], + [ + "SAR2_RSTB_WAIT", + 20, + 8 + ] + ] + }, + { + "name": "SAR_MEAS_CTRL", + "offset": "0x10", + "reset": "0x0707338F", + "fields": [ + [ + "XPD_SAR_AMP_FSM", + 0, + 4 + ], + [ + "AMP_RST_FB_FSM", + 4, + 4 + ], + [ + "AMP_SHORT_REF_FSM", + 8, + 4 + ], + [ + "AMP_SHORT_REF_GND_FSM", + 12, + 4 + ], + [ + "XPD_SAR_FSM", + 16, + 4 + ], + [ + "SAR_RSTB_FSM", + 20, + 4 + ], + [ + "SAR2_XPD_WAIT", + 24, + 8 + ] + ] + }, + { + "name": "SAR_READ_STATUS2", + "offset": "0x14", + "fields": [ + [ + "SAR2_READER_STATUS", + 0, + 32, + "r" + ] + ] + }, + { + "name": "ULP_CP_SLEEP_CYC0", + "offset": "0x18", + "reset": "0x000000C8", + "fields": [ + [ + "SLEEP_CYCLES_S0", + 0, + 32 + ] + ] + }, + { + "name": "ULP_CP_SLEEP_CYC1", + "offset": "0x1C", + "reset": "0x00000064", + "fields": [ + [ + "SLEEP_CYCLES_S1", + 0, + 32 + ] + ] + }, + { + "name": "ULP_CP_SLEEP_CYC2", + "offset": "0x20", + "reset": "0x00000032", + "fields": [ + [ + "SLEEP_CYCLES_S2", + 0, + 32 + ] + ] + }, + { + "name": "ULP_CP_SLEEP_CYC3", + "offset": "0x24", + "reset": "0x00000028", + "fields": [ + [ + "SLEEP_CYCLES_S3", + 0, + 32 + ] + ] + }, + { + "name": "ULP_CP_SLEEP_CYC4", + "offset": "0x28", + "reset": "0x00000014", + "fields": [ + [ + "SLEEP_CYCLES_S4", + 0, + 32 + ] + ] + }, + { + "name": "SAR_START_FORCE", + "offset": "0x2C", + "reset": "0x0000000F", + "fields": [ + [ + "SAR1_BIT_WIDTH", + 0, + 2 + ], + [ + "SAR2_BIT_WIDTH", + 2, + 2 + ], + [ + "SAR2_EN_TEST", + 4, + 1 + ], + [ + "SAR2_PWDET_CCT", + 5, + 3 + ], + [ + "ULP_CP_FORCE_START_TOP", + 8, + 1 + ], + [ + "ULP_CP_START_TOP", + 9, + 1 + ], + [ + "SARCLK_EN", + 10, + 1 + ], + [ + "PC_INIT", + 11, + 11 + ], + [ + "SAR2_STOP", + 22, + 1 + ], + [ + "SAR1_STOP", + 23, + 1 + ], + [ + "SAR2_PWDET_EN", + 24, + 1 + ] + ] + }, + { + "name": "SAR_MEM_WR_CTRL", + "offset": "0x30", + "reset": "0x00100200", + "fields": [ + [ + "MEM_WR_ADDR_INIT", + 0, + 11 + ], + [ + "MEM_WR_ADDR_SIZE", + 11, + 11 + ], + [ + "RTC_MEM_WR_OFFST_CLR", + 22, + 1, + "w" + ] + ] + }, + { + "name": "SAR_ATTEN1", + "offset": "0x34", + "reset": "0xFFFFFFFF", + "fields": [ + [ + "SAR1_ATTEN", + 0, + 32 + ] + ] + }, + { + "name": "SAR_ATTEN2", + "offset": "0x38", + "reset": "0xFFFFFFFF", + "fields": [ + [ + "SAR2_ATTEN", + 0, + 32 + ] + ] + }, + { + "name": "SAR_SLAVE_ADDR1", + "offset": "0x3C", + "fields": [ + [ + "I2C_SLAVE_ADDR1", + 0, + 11 + ], + [ + "I2C_SLAVE_ADDR0", + 11, + 11 + ], + [ + "MEAS_STATUS", + 22, + 8, + "r" + ] + ] + }, + { + "name": "SAR_SLAVE_ADDR2", + "offset": "0x40", + "fields": [ + [ + "I2C_SLAVE_ADDR3", + 0, + 11 + ], + [ + "I2C_SLAVE_ADDR2", + 11, + 11 + ] + ] + }, + { + "name": "SAR_SLAVE_ADDR3", + "offset": "0x44", + "fields": [ + [ + "I2C_SLAVE_ADDR5", + 0, + 11 + ], + [ + "I2C_SLAVE_ADDR4", + 11, + 11 + ], + [ + "TSENS_OUT", + 22, + 8, + "r" + ], + [ + "TSENS_RDY_OUT", + 30, + 1, + "r" + ] + ] + }, + { + "name": "SAR_SLAVE_ADDR4", + "offset": "0x48", + "fields": [ + [ + "I2C_SLAVE_ADDR7", + 0, + 11 + ], + [ + "I2C_SLAVE_ADDR6", + 11, + 11 + ], + [ + "I2C_RDATA", + 22, + 8, + "r" + ], + [ + "I2C_DONE", + 30, + 1, + "r" + ] + ] + }, + { + "name": "SAR_TSENS_CTRL", + "offset": "0x4C", + "reset": "0x00066002", + "fields": [ + [ + "TSENS_XPD_WAIT", + 0, + 12 + ], + [ + "TSENS_XPD_FORCE", + 12, + 1 + ], + [ + "TSENS_CLK_INV", + 13, + 1 + ], + [ + "TSENS_CLK_GATED", + 14, + 1 + ], + [ + "TSENS_IN_INV", + 15, + 1 + ], + [ + "TSENS_CLK_DIV", + 16, + 8 + ], + [ + "TSENS_POWER_UP", + 24, + 1 + ], + [ + "TSENS_POWER_UP_FORCE", + 25, + 1 + ], + [ + "TSENS_DUMP_OUT", + 26, + 1 + ] + ] + }, + { + "name": "SAR_I2C_CTRL", + "offset": "0x50", + "fields": [ + [ + "SAR_I2C_CTRL", + 0, + 28 + ], + [ + "SAR_I2C_START", + 28, + 1 + ], + [ + "SAR_I2C_START_FORCE", + 29, + 1 + ] + ] + }, + { + "name": "SAR_MEAS_START1", + "offset": "0x54", + "fields": [ + [ + "MEAS1_DATA_SAR", + 0, + 16, + "r" + ], + [ + "MEAS1_DONE_SAR", + 16, + 1, + "r" + ], + [ + "MEAS1_START_SAR", + 17, + 1 + ], + [ + "MEAS1_START_FORCE", + 18, + 1 + ], + [ + "SAR1_EN_PAD", + 19, + 12 + ], + [ + "SAR1_EN_PAD_FORCE", + 31, + 1 + ] + ] + }, + { + "name": "SAR_TOUCH_CTRL1", + "offset": "0x58", + "reset": "0x02041000", + "fields": [ + [ + "TOUCH_MEAS_DELAY", + 0, + 16 + ], + [ + "TOUCH_XPD_WAIT", + 16, + 8 + ], + [ + "TOUCH_OUT_SEL", + 24, + 1 + ], + [ + "TOUCH_OUT_1EN", + 25, + 1 + ], + [ + "XPD_HALL_FORCE", + 26, + 1 + ], + [ + "HALL_PHASE_FORCE", + 27, + 1 + ] + ] + }, + { + "name": "SAR_TOUCH_THRES1", + "offset": "0x5C", + "fields": [ + [ + "TOUCH_OUT_TH1", + 0, + 16 + ], + [ + "TOUCH_OUT_TH0", + 16, + 16 + ] + ] + }, + { + "name": "SAR_TOUCH_THRES2", + "offset": "0x60", + "fields": [ + [ + "TOUCH_OUT_TH3", + 0, + 16 + ], + [ + "TOUCH_OUT_TH2", + 16, + 16 + ] + ] + }, + { + "name": "SAR_TOUCH_THRES3", + "offset": "0x64", + "fields": [ + [ + "TOUCH_OUT_TH5", + 0, + 16 + ], + [ + "TOUCH_OUT_TH4", + 16, + 16 + ] + ] + }, + { + "name": "SAR_TOUCH_THRES4", + "offset": "0x68", + "fields": [ + [ + "TOUCH_OUT_TH7", + 0, + 16 + ], + [ + "TOUCH_OUT_TH6", + 16, + 16 + ] + ] + }, + { + "name": "SAR_TOUCH_THRES5", + "offset": "0x6C", + "fields": [ + [ + "TOUCH_OUT_TH9", + 0, + 16 + ], + [ + "TOUCH_OUT_TH8", + 16, + 16 + ] + ] + }, + { + "name": "SAR_TOUCH_OUT1", + "offset": "0x70", + "fields": [ + [ + "TOUCH_MEAS_OUT1", + 0, + 16, + "r" + ], + [ + "TOUCH_MEAS_OUT0", + 16, + 16, + "r" + ] + ] + }, + { + "name": "SAR_TOUCH_OUT2", + "offset": "0x74", + "fields": [ + [ + "TOUCH_MEAS_OUT3", + 0, + 16, + "r" + ], + [ + "TOUCH_MEAS_OUT2", + 16, + 16, + "r" + ] + ] + }, + { + "name": "SAR_TOUCH_OUT3", + "offset": "0x78", + "fields": [ + [ + "TOUCH_MEAS_OUT5", + 0, + 16, + "r" + ], + [ + "TOUCH_MEAS_OUT4", + 16, + 16, + "r" + ] + ] + }, + { + "name": "SAR_TOUCH_OUT4", + "offset": "0x7C", + "fields": [ + [ + "TOUCH_MEAS_OUT7", + 0, + 16, + "r" + ], + [ + "TOUCH_MEAS_OUT6", + 16, + 16, + "r" + ] + ] + }, + { + "name": "SAR_TOUCH_OUT5", + "offset": "0x80", + "fields": [ + [ + "TOUCH_MEAS_OUT9", + 0, + 16, + "r" + ], + [ + "TOUCH_MEAS_OUT8", + 16, + 16, + "r" + ] + ] + }, + { + "name": "SAR_TOUCH_CTRL2", + "offset": "0x84", + "reset": "0x00400800", + "fields": [ + [ + "TOUCH_MEAS_EN", + 0, + 10, + "r" + ], + [ + "TOUCH_MEAS_DONE", + 10, + 1, + "r" + ], + [ + "TOUCH_START_FSM_EN", + 11, + 1 + ], + [ + "TOUCH_START_EN", + 12, + 1 + ], + [ + "TOUCH_START_FORCE", + 13, + 1 + ], + [ + "TOUCH_SLEEP_CYCLES", + 14, + 16 + ], + [ + "TOUCH_MEAS_EN_CLR", + 30, + 1, + "w" + ] + ] + }, + { + "name": "SAR_TOUCH_ENABLE", + "offset": "0x8C", + "reset": "0x3FFFFFFF", + "fields": [ + [ + "TOUCH_PAD_WORKEN", + 0, + 10 + ], + [ + "TOUCH_PAD_OUTEN2", + 10, + 10 + ], + [ + "TOUCH_PAD_OUTEN1", + 20, + 10 + ] + ] + }, + { + "name": "SAR_READ_CTRL2", + "offset": "0x90", + "reset": "0x00070902", + "fields": [ + [ + "SAR2_CLK_DIV", + 0, + 8 + ], + [ + "SAR2_SAMPLE_CYCLE", + 8, + 8 + ], + [ + "SAR2_SAMPLE_BIT", + 16, + 2 + ], + [ + "SAR2_CLK_GATED", + 18, + 1 + ], + [ + "SAR2_SAMPLE_NUM", + 19, + 8 + ], + [ + "SAR2_PWDET_FORCE", + 27, + 1 + ], + [ + "SAR2_DIG_FORCE", + 28, + 1 + ], + [ + "SAR2_DATA_INV", + 29, + 1 + ] + ] + }, + { + "name": "SAR_MEAS_START2", + "offset": "0x94", + "fields": [ + [ + "MEAS2_DATA_SAR", + 0, + 16, + "r" + ], + [ + "MEAS2_DONE_SAR", + 16, + 1, + "r" + ], + [ + "MEAS2_START_SAR", + 17, + 1 + ], + [ + "MEAS2_START_FORCE", + 18, + 1 + ], + [ + "SAR2_EN_PAD", + 19, + 12 + ], + [ + "SAR2_EN_PAD_FORCE", + 31, + 1 + ] + ] + }, + { + "name": "SAR_DAC_CTRL1", + "offset": "0x98", + "fields": [ + [ + "SW_FSTEP", + 0, + 16 + ], + [ + "SW_TONE_EN", + 16, + 1 + ], + [ + "DEBUG_BIT_SEL", + 17, + 5 + ], + [ + "DAC_DIG_FORCE", + 22, + 1 + ], + [ + "DAC_CLK_FORCE_LOW", + 23, + 1 + ], + [ + "DAC_CLK_FORCE_HIGH", + 24, + 1 + ], + [ + "DAC_CLK_INV", + 25, + 1 + ] + ] + }, + { + "name": "SAR_DAC_CTRL2", + "offset": "0x9C", + "reset": "0x03000000", + "fields": [ + [ + "DAC_DC1", + 0, + 8 + ], + [ + "DAC_DC2", + 8, + 8 + ], + [ + "DAC_SCALE1", + 16, + 2 + ], + [ + "DAC_SCALE2", + 18, + 2 + ], + [ + "DAC_INV1", + 20, + 2 + ], + [ + "DAC_INV2", + 22, + 2 + ], + [ + "DAC_CW_EN1", + 24, + 1 + ], + [ + "DAC_CW_EN2", + 25, + 1 + ] + ] + }, + { + "name": "SAR_MEAS_CTRL2", + "offset": "0xA0", + "reset": "0x00000003", + "fields": [ + [ + "SAR1_DAC_XPD_FSM", + 0, + 4 + ], + [ + "SAR1_DAC_XPD_FSM_IDLE", + 4, + 1 + ], + [ + "XPD_SAR_AMP_FSM_IDLE", + 5, + 1 + ], + [ + "AMP_RST_FB_FSM_IDLE", + 6, + 1 + ], + [ + "AMP_SHORT_REF_FSM_IDLE", + 7, + 1 + ], + [ + "AMP_SHORT_REF_GND_FSM_IDLE", + 8, + 1 + ], + [ + "XPD_SAR_FSM_IDLE", + 9, + 1 + ], + [ + "SAR_RSTB_FSM_IDLE", + 10, + 1 + ], + [ + "SAR2_RSTB_FORCE", + 11, + 2 + ], + [ + "AMP_RST_FB_FORCE", + 13, + 2 + ], + [ + "AMP_SHORT_REF_FORCE", + 15, + 2 + ], + [ + "AMP_SHORT_REF_GND_FORCE", + 17, + 2 + ] + ] + }, + { + "name": "SAR_NOUSE", + "offset": "0xF8", + "fields": [ + [ + "SAR_NOUSE", + 0, + 32 + ] + ] + }, + { + "name": "SARDATE", + "offset": "0xFC", + "reset": "0x01605180", + "fields": [ + [ + "SAR_DATE", + 0, + 28 + ] + ] + } + ] + }, + { + "name": "RTC_I2C", + "base": "0x3FF48C00", + "registers": [ + { + "name": "SCL_LOW_PERIOD", + "offset": "0x00", + "fields": [ + [ + "SCL_LOW_PERIOD", + 0, + 25 + ] + ] + }, + { + "name": "CTRL", + "offset": "0x04", + "fields": [ + [ + "SDA_FORCE_OUT", + 0, + 1 + ], + [ + "SCL_FORCE_OUT", + 1, + 1 + ], + [ + "MS_MODE", + 4, + 1 + ], + [ + "TRANS_START", + 5, + 1 + ], + [ + "TX_LSB_FIRST", + 6, + 1 + ], + [ + "RX_LSB_FIRST", + 7, + 1 + ] + ] + }, + { + "name": "DEBUG_STATUS", + "offset": "0x08", + "fields": [ + [ + "ACK_VAL", + 0, + 1 + ], + [ + "SLAVE_RW", + 1, + 1 + ], + [ + "TIMED_OUT", + 2, + 1 + ], + [ + "ARB_LOST", + 3, + 1 + ], + [ + "BUS_BUSY", + 4, + 1 + ], + [ + "SLAVE_ADDR_MATCH", + 5, + 1 + ], + [ + "BYTE_TRANS", + 6, + 1 + ], + [ + "MAIN_STATE", + 25, + 3 + ], + [ + "SCL_STATE", + 28, + 3 + ] + ] + }, + { + "name": "TIMEOUT", + "offset": "0x0C", + "fields": [ + [ + "TIMEOUT", + 0, + 20 + ] + ] + }, + { + "name": "SLAVE_ADDR", + "offset": "0x10", + "fields": [ + [ + "SLAVE_ADDR", + 0, + 15 + ], + [ + "_10BIT", + 31, + 1 + ] + ] + }, + { + "name": "DATA", + "offset": "0x1C" + }, + { + "name": "INT_RAW", + "offset": "0x20", + "fields": [ + [ + "SLAVE_TRANS_COMPLETE_INT_RAW", + 3, + 1 + ], + [ + "ARBITRATION_LOST_INT_RAW", + 4, + 1 + ], + [ + "MASTER_TRANS_COMPLETE_INT_RAW", + 5, + 1 + ], + [ + "TRANS_COMPLETE_INT_RAW", + 6, + 1 + ], + [ + "TIME_OUT_INT_RAW", + 7, + 1, + "r" + ] + ] + }, + { + "name": "INT_CLR", + "offset": "0x24", + "fields": [ + [ + "SLAVE_TRANS_COMPLETE_INT_CLR", + 4, + 1 + ], + [ + "ARBITRATION_LOST_INT_CLR", + 5, + 1 + ], + [ + "MASTER_TRANS_COMPLETE_INT_CLR", + 6, + 1 + ], + [ + "TRANS_COMPLETE_INT_CLR", + 7, + 1 + ], + [ + "TIME_OUT_INT_CLR", + 8, + 1, + "w" + ] + ] + }, + { + "name": "INT_EN", + "offset": "0x28" + }, + { + "name": "INT_ST", + "offset": "0x2C" + }, + { + "name": "SDA_DUTY", + "offset": "0x30", + "fields": [ + [ + "SDA_DUTY", + 0, + 20 + ] + ] + }, + { + "name": "SCL_HIGH_PERIOD", + "offset": "0x38", + "fields": [ + [ + "SCL_HIGH_PERIOD", + 0, + 20 + ] + ] + }, + { + "name": "SCL_START_PERIOD", + "offset": "0x40", + "fields": [ + [ + "SCL_START_PERIOD", + 0, + 20 + ] + ] + }, + { + "name": "SCL_STOP_PERIOD", + "offset": "0x44", + "fields": [ + [ + "SCL_STOP_PERIOD", + 0, + 20 + ] + ] + }, + { + "name": "CMD", + "offset": "0x48", + "fields": [ + [ + "VAL", + 0, + 14 + ], + [ + "DONE", + 31, + 1 + ] + ] + } + ] + }, + { + "name": "IO_MUX", + "base": "0x3FF49000", + "registers": [ + { + "name": "PIN_CTRL", + "offset": "0x00", + "fields": [ + [ + "CLK1", + 0, + 4 + ], + [ + "CLK2", + 4, + 4 + ], + [ + "CLK3", + 8, + 4 + ] + ] + }, + { + "name": "GPIO36", + "offset": "0x04", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO37", + "offset": "0x08", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO38", + "offset": "0x0C", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO39", + "offset": "0x10", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO34", + "offset": "0x14", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO35", + "offset": "0x18", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO32", + "offset": "0x1C", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO33", + "offset": "0x20", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO25", + "offset": "0x24", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO26", + "offset": "0x28", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO27", + "offset": "0x2C", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO14", + "offset": "0x30", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO12", + "offset": "0x34", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO13", + "offset": "0x38", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO15", + "offset": "0x3C", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO2", + "offset": "0x40", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO0", + "offset": "0x44", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO4", + "offset": "0x48", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO16", + "offset": "0x4C", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO17", + "offset": "0x50", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO9", + "offset": "0x54", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO10", + "offset": "0x58", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO11", + "offset": "0x5C", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO6", + "offset": "0x60", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO7", + "offset": "0x64", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO8", + "offset": "0x68", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO5", + "offset": "0x6C", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO18", + "offset": "0x70", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO19", + "offset": "0x74", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO20", + "offset": "0x78", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO21", + "offset": "0x7C", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO22", + "offset": "0x80", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO3", + "offset": "0x84", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO1", + "offset": "0x88", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO23", + "offset": "0x8C", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + }, + { + "name": "GPIO24", + "offset": "0x90", + "fields": [ + [ + "MCU_OE", + 0, + 1 + ], + [ + "SLP_SEL", + 1, + 1 + ], + [ + "MCU_WPD", + 2, + 1 + ], + [ + "MCU_WPU", + 3, + 1 + ], + [ + "MCU_IE", + 4, + 1 + ], + [ + "MCU_DRV", + 5, + 2 + ], + [ + "FUN_WPD", + 7, + 1 + ], + [ + "FUN_WPU", + 8, + 1 + ], + [ + "FUN_IE", + 9, + 1 + ], + [ + "FUN_DRV", + 10, + 2 + ], + [ + "MCU_SEL", + 12, + 3 + ] + ] + } + ] + }, + { + "name": "HINF", + "base": "0x3FF4B000", + "registers": [ + { + "name": "CFG_DATA0", + "offset": "0x00", + "reset": "0x22226666", + "fields": [ + [ + "USER_ID_FN1", + 0, + 16 + ], + [ + "DEVICE_ID_FN1", + 16, + 16 + ] + ] + }, + { + "name": "CFG_DATA1", + "offset": "0x04", + "reset": "0x01110011", + "fields": [ + [ + "SDIO_ENABLE", + 0, + 1 + ], + [ + "SDIO_IOREADY1", + 1, + 1 + ], + [ + "HIGHSPEED_ENABLE", + 2, + 1 + ], + [ + "HIGHSPEED_MODE", + 3, + 1, + "r" + ], + [ + "SDIO_CD_ENABLE", + 4, + 1 + ], + [ + "SDIO_IOREADY2", + 5, + 1 + ], + [ + "SDIO_INT_MASK", + 6, + 1 + ], + [ + "IOENABLE2", + 7, + 1, + "r" + ], + [ + "CD_DISABLE", + 8, + 1, + "r" + ], + [ + "FUNC1_EPS", + 9, + 1, + "r" + ], + [ + "EMP", + 10, + 1, + "r" + ], + [ + "IOENABLE1", + 11, + 1, + "r" + ], + [ + "SDIO20_CONF0", + 12, + 4 + ], + [ + "SDIO_VER", + 16, + 12 + ], + [ + "FUNC2_EPS", + 28, + 1, + "r" + ], + [ + "SDIO20_CONF1", + 29, + 3 + ] + ] + }, + { + "name": "CFG_DATA7", + "offset": "0x1C", + "reset": "0x00020000", + "fields": [ + [ + "PIN_STATE", + 0, + 8 + ], + [ + "CHIP_STATE", + 8, + 8 + ], + [ + "SDIO_RST", + 16, + 1 + ], + [ + "SDIO_IOREADY0", + 17, + 1 + ] + ] + }, + { + "name": "CIS_CONF0", + "offset": "0x20", + "reset": "0xFFFFFFFF", + "fields": [ + [ + "CIS_CONF_W0", + 0, + 32 + ] + ] + }, + { + "name": "CIS_CONF1", + "offset": "0x24", + "reset": "0xFFFFFFFF", + "fields": [ + [ + "CIS_CONF_W1", + 0, + 32 + ] + ] + }, + { + "name": "CIS_CONF2", + "offset": "0x28", + "reset": "0xFFFFFFFF", + "fields": [ + [ + "CIS_CONF_W2", + 0, + 32 + ] + ] + }, + { + "name": "CIS_CONF3", + "offset": "0x2C", + "reset": "0xFFFFFFFF", + "fields": [ + [ + "CIS_CONF_W3", + 0, + 32 + ] + ] + }, + { + "name": "CIS_CONF4", + "offset": "0x30", + "reset": "0xFFFFFFFF", + "fields": [ + [ + "CIS_CONF_W4", + 0, + 32 + ] + ] + }, + { + "name": "CIS_CONF5", + "offset": "0x34", + "reset": "0xFFFFFFFF", + "fields": [ + [ + "CIS_CONF_W5", + 0, + 32 + ] + ] + }, + { + "name": "CIS_CONF6", + "offset": "0x38", + "reset": "0xFFFFFFFF", + "fields": [ + [ + "CIS_CONF_W6", + 0, + 32 + ] + ] + }, + { + "name": "CIS_CONF7", + "offset": "0x3C", + "reset": "0xFFFFFFFF", + "fields": [ + [ + "CIS_CONF_W7", + 0, + 32 + ] + ] + }, + { + "name": "CFG_DATA16", + "offset": "0x40", + "reset": "0x33336666", + "fields": [ + [ + "USER_ID_FN2", + 0, + 16 + ], + [ + "DEVICE_ID_FN2", + 16, + 16 + ] + ] + }, + { + "name": "DATE", + "offset": "0xFC", + "reset": "0x15030200", + "fields": [ + [ + "SDIO_DATE", + 0, + 32 + ] + ] + } + ] + }, + { + "name": "UHCI1", + "base": "0x3FF4C000", + "interrupts": [ + "UHCI1" + ], + "derived_from": "UHCI0" + }, + { + "name": "I2S0", + "base": "0x3FF4F000", + "interrupts": [ + "I2S0" + ], + "registers": [ + { + "name": "CONF", + "offset": "0x08", + "reset": "0x00030300", + "fields": [ + [ + "TX_RESET", + 0, + 1 + ], + [ + "RX_RESET", + 1, + 1 + ], + [ + "TX_FIFO_RESET", + 2, + 1 + ], + [ + "RX_FIFO_RESET", + 3, + 1 + ], + [ + "TX_START", + 4, + 1 + ], + [ + "RX_START", + 5, + 1 + ], + [ + "TX_SLAVE_MOD", + 6, + 1 + ], + [ + "RX_SLAVE_MOD", + 7, + 1 + ], + [ + "TX_RIGHT_FIRST", + 8, + 1 + ], + [ + "RX_RIGHT_FIRST", + 9, + 1 + ], + [ + "TX_MSB_SHIFT", + 10, + 1 + ], + [ + "RX_MSB_SHIFT", + 11, + 1 + ], + [ + "TX_SHORT_SYNC", + 12, + 1 + ], + [ + "RX_SHORT_SYNC", + 13, + 1 + ], + [ + "TX_MONO", + 14, + 1 + ], + [ + "RX_MONO", + 15, + 1 + ], + [ + "TX_MSB_RIGHT", + 16, + 1 + ], + [ + "RX_MSB_RIGHT", + 17, + 1 + ], + [ + "SIG_LOOPBACK", + 18, + 1 + ] + ] + }, + { + "name": "INT_RAW", + "offset": "0x0C", + "fields": [ + [ + "RX_TAKE_DATA_INT_RAW", + 0, + 1, + "r" + ], + [ + "TX_PUT_DATA_INT_RAW", + 1, + 1, + "r" + ], + [ + "RX_WFULL_INT_RAW", + 2, + 1, + "r" + ], + [ + "RX_REMPTY_INT_RAW", + 3, + 1, + "r" + ], + [ + "TX_WFULL_INT_RAW", + 4, + 1, + "r" + ], + [ + "TX_REMPTY_INT_RAW", + 5, + 1, + "r" + ], + [ + "RX_HUNG_INT_RAW", + 6, + 1, + "r" + ], + [ + "TX_HUNG_INT_RAW", + 7, + 1, + "r" + ], + [ + "IN_DONE_INT_RAW", + 8, + 1, + "r" + ], + [ + "IN_SUC_EOF_INT_RAW", + 9, + 1, + "r" + ], + [ + "IN_ERR_EOF_INT_RAW", + 10, + 1, + "r" + ], + [ + "OUT_DONE_INT_RAW", + 11, + 1, + "r" + ], + [ + "OUT_EOF_INT_RAW", + 12, + 1, + "r" + ], + [ + "IN_DSCR_ERR_INT_RAW", + 13, + 1, + "r" + ], + [ + "OUT_DSCR_ERR_INT_RAW", + 14, + 1, + "r" + ], + [ + "IN_DSCR_EMPTY_INT_RAW", + 15, + 1, + "r" + ], + [ + "OUT_TOTAL_EOF_INT_RAW", + 16, + 1, + "r" + ] + ] + }, + { + "name": "INT_ST", + "offset": "0x10", + "fields": [ + [ + "RX_TAKE_DATA_INT_ST", + 0, + 1, + "r" + ], + [ + "TX_PUT_DATA_INT_ST", + 1, + 1, + "r" + ], + [ + "RX_WFULL_INT_ST", + 2, + 1, + "r" + ], + [ + "RX_REMPTY_INT_ST", + 3, + 1, + "r" + ], + [ + "TX_WFULL_INT_ST", + 4, + 1, + "r" + ], + [ + "TX_REMPTY_INT_ST", + 5, + 1, + "r" + ], + [ + "RX_HUNG_INT_ST", + 6, + 1, + "r" + ], + [ + "TX_HUNG_INT_ST", + 7, + 1, + "r" + ], + [ + "IN_DONE_INT_ST", + 8, + 1, + "r" + ], + [ + "IN_SUC_EOF_INT_ST", + 9, + 1, + "r" + ], + [ + "IN_ERR_EOF_INT_ST", + 10, + 1, + "r" + ], + [ + "OUT_DONE_INT_ST", + 11, + 1, + "r" + ], + [ + "OUT_EOF_INT_ST", + 12, + 1, + "r" + ], + [ + "IN_DSCR_ERR_INT_ST", + 13, + 1, + "r" + ], + [ + "OUT_DSCR_ERR_INT_ST", + 14, + 1, + "r" + ], + [ + "IN_DSCR_EMPTY_INT_ST", + 15, + 1, + "r" + ], + [ + "OUT_TOTAL_EOF_INT_ST", + 16, + 1, + "r" + ] + ] + }, + { + "name": "INT_ENA", + "offset": "0x14", + "fields": [ + [ + "RX_TAKE_DATA_INT_ENA", + 0, + 1 + ], + [ + "TX_PUT_DATA_INT_ENA", + 1, + 1 + ], + [ + "RX_WFULL_INT_ENA", + 2, + 1 + ], + [ + "RX_REMPTY_INT_ENA", + 3, + 1 + ], + [ + "TX_WFULL_INT_ENA", + 4, + 1 + ], + [ + "TX_REMPTY_INT_ENA", + 5, + 1 + ], + [ + "RX_HUNG_INT_ENA", + 6, + 1 + ], + [ + "TX_HUNG_INT_ENA", + 7, + 1 + ], + [ + "IN_DONE_INT_ENA", + 8, + 1 + ], + [ + "IN_SUC_EOF_INT_ENA", + 9, + 1 + ], + [ + "IN_ERR_EOF_INT_ENA", + 10, + 1 + ], + [ + "OUT_DONE_INT_ENA", + 11, + 1 + ], + [ + "OUT_EOF_INT_ENA", + 12, + 1 + ], + [ + "IN_DSCR_ERR_INT_ENA", + 13, + 1 + ], + [ + "OUT_DSCR_ERR_INT_ENA", + 14, + 1 + ], + [ + "IN_DSCR_EMPTY_INT_ENA", + 15, + 1 + ], + [ + "OUT_TOTAL_EOF_INT_ENA", + 16, + 1 + ] + ] + }, + { + "name": "INT_CLR", + "offset": "0x18", + "fields": [ + [ + "TAKE_DATA_INT_CLR", + 0, + 1, + "w" + ], + [ + "PUT_DATA_INT_CLR", + 1, + 1, + "w" + ], + [ + "RX_WFULL_INT_CLR", + 2, + 1, + "w" + ], + [ + "RX_REMPTY_INT_CLR", + 3, + 1, + "w" + ], + [ + "TX_WFULL_INT_CLR", + 4, + 1, + "w" + ], + [ + "TX_REMPTY_INT_CLR", + 5, + 1, + "w" + ], + [ + "RX_HUNG_INT_CLR", + 6, + 1, + "w" + ], + [ + "TX_HUNG_INT_CLR", + 7, + 1, + "w" + ], + [ + "IN_DONE_INT_CLR", + 8, + 1, + "w" + ], + [ + "IN_SUC_EOF_INT_CLR", + 9, + 1, + "w" + ], + [ + "IN_ERR_EOF_INT_CLR", + 10, + 1, + "w" + ], + [ + "OUT_DONE_INT_CLR", + 11, + 1, + "w" + ], + [ + "OUT_EOF_INT_CLR", + 12, + 1, + "w" + ], + [ + "IN_DSCR_ERR_INT_CLR", + 13, + 1, + "w" + ], + [ + "OUT_DSCR_ERR_INT_CLR", + 14, + 1, + "w" + ], + [ + "IN_DSCR_EMPTY_INT_CLR", + 15, + 1, + "w" + ], + [ + "OUT_TOTAL_EOF_INT_CLR", + 16, + 1, + "w" + ] + ] + }, + { + "name": "TIMING", + "offset": "0x1C", + "fields": [ + [ + "TX_BCK_IN_DELAY", + 0, + 2 + ], + [ + "TX_WS_IN_DELAY", + 2, + 2 + ], + [ + "RX_BCK_IN_DELAY", + 4, + 2 + ], + [ + "RX_WS_IN_DELAY", + 6, + 2 + ], + [ + "RX_SD_IN_DELAY", + 8, + 2 + ], + [ + "TX_BCK_OUT_DELAY", + 10, + 2 + ], + [ + "TX_WS_OUT_DELAY", + 12, + 2 + ], + [ + "TX_SD_OUT_DELAY", + 14, + 2 + ], + [ + "RX_WS_OUT_DELAY", + 16, + 2 + ], + [ + "RX_BCK_OUT_DELAY", + 18, + 2 + ], + [ + "TX_DSYNC_SW", + 20, + 1 + ], + [ + "RX_DSYNC_SW", + 21, + 1 + ], + [ + "DATA_ENABLE_DELAY", + 22, + 2 + ], + [ + "TX_BCK_IN_INV", + 24, + 1 + ] + ] + }, + { + "name": "FIFO_CONF", + "offset": "0x20", + "reset": "0x00001820", + "fields": [ + [ + "RX_DATA_NUM", + 0, + 6 + ], + [ + "TX_DATA_NUM", + 6, + 6 + ], + [ + "DSCR_EN", + 12, + 1 + ], + [ + "TX_FIFO_MOD", + 13, + 3 + ], + [ + "RX_FIFO_MOD", + 16, + 3 + ], + [ + "TX_FIFO_MOD_FORCE_EN", + 19, + 1 + ], + [ + "RX_FIFO_MOD_FORCE_EN", + 20, + 1 + ] + ] + }, + { + "name": "RXEOF_NUM", + "offset": "0x24", + "reset": "0x00000040", + "fields": [ + [ + "RX_EOF_NUM", + 0, + 32 + ] + ] + }, + { + "name": "CONF_SIGLE_DATA", + "offset": "0x28", + "fields": [ + [ + "SIGLE_DATA", + 0, + 32 + ] + ] + }, + { + "name": "CONF_CHAN", + "offset": "0x2C", + "fields": [ + [ + "TX_CHAN_MOD", + 0, + 3 + ], + [ + "RX_CHAN_MOD", + 3, + 2 + ] + ] + }, + { + "name": "OUT_LINK", + "offset": "0x30", + "fields": [ + [ + "OUTLINK_ADDR", + 0, + 20 + ], + [ + "OUTLINK_STOP", + 28, + 1 + ], + [ + "OUTLINK_START", + 29, + 1 + ], + [ + "OUTLINK_RESTART", + 30, + 1 + ], + [ + "OUTLINK_PARK", + 31, + 1, + "r" + ] + ] + }, + { + "name": "IN_LINK", + "offset": "0x34", + "fields": [ + [ + "INLINK_ADDR", + 0, + 20 + ], + [ + "INLINK_STOP", + 28, + 1 + ], + [ + "INLINK_START", + 29, + 1 + ], + [ + "INLINK_RESTART", + 30, + 1 + ], + [ + "INLINK_PARK", + 31, + 1, + "r" + ] + ] + }, + { + "name": "OUT_EOF_DES_ADDR", + "offset": "0x38", + "fields": [ + [ + "OUT_EOF_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "IN_EOF_DES_ADDR", + "offset": "0x3C", + "fields": [ + [ + "IN_SUC_EOF_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "OUT_EOF_BFR_DES_ADDR", + "offset": "0x40", + "fields": [ + [ + "OUT_EOF_BFR_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "AHB_TEST", + "offset": "0x44", + "fields": [ + [ + "AHB_TESTMODE", + 0, + 3 + ], + [ + "AHB_TESTADDR", + 4, + 2 + ] + ] + }, + { + "name": "INLINK_DSCR", + "offset": "0x48", + "fields": [ + [ + "INLINK_DSCR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "INLINK_DSCR_BF0", + "offset": "0x4C", + "fields": [ + [ + "INLINK_DSCR_BF0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "INLINK_DSCR_BF1", + "offset": "0x50", + "fields": [ + [ + "INLINK_DSCR_BF1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "OUTLINK_DSCR", + "offset": "0x54", + "fields": [ + [ + "OUTLINK_DSCR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "OUTLINK_DSCR_BF0", + "offset": "0x58", + "fields": [ + [ + "OUTLINK_DSCR_BF0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "OUTLINK_DSCR_BF1", + "offset": "0x5C", + "fields": [ + [ + "OUTLINK_DSCR_BF1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "LC_CONF", + "offset": "0x60", + "reset": "0x00000100", + "fields": [ + [ + "IN_RST", + 0, + 1 + ], + [ + "OUT_RST", + 1, + 1 + ], + [ + "AHBM_FIFO_RST", + 2, + 1 + ], + [ + "AHBM_RST", + 3, + 1 + ], + [ + "OUT_LOOP_TEST", + 4, + 1 + ], + [ + "IN_LOOP_TEST", + 5, + 1 + ], + [ + "OUT_AUTO_WRBACK", + 6, + 1 + ], + [ + "OUT_NO_RESTART_CLR", + 7, + 1 + ], + [ + "OUT_EOF_MODE", + 8, + 1 + ], + [ + "OUTDSCR_BURST_EN", + 9, + 1 + ], + [ + "INDSCR_BURST_EN", + 10, + 1 + ], + [ + "OUT_DATA_BURST_EN", + 11, + 1 + ], + [ + "CHECK_OWNER", + 12, + 1 + ], + [ + "MEM_TRANS_EN", + 13, + 1 + ] + ] + }, + { + "name": "OUTFIFO_PUSH", + "offset": "0x64", + "fields": [ + [ + "OUTFIFO_WDATA", + 0, + 9 + ], + [ + "OUTFIFO_PUSH", + 16, + 1 + ] + ] + }, + { + "name": "INFIFO_POP", + "offset": "0x68", + "fields": [ + [ + "INFIFO_RDATA", + 0, + 12, + "r" + ], + [ + "INFIFO_POP", + 16, + 1 + ] + ] + }, + { + "name": "LC_STATE0", + "offset": "0x6C", + "fields": [ + [ + "LC_STATE0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "LC_STATE1", + "offset": "0x70", + "fields": [ + [ + "LC_STATE1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "LC_HUNG_CONF", + "offset": "0x74", + "reset": "0x00000810", + "fields": [ + [ + "LC_FIFO_TIMEOUT", + 0, + 8 + ], + [ + "LC_FIFO_TIMEOUT_SHIFT", + 8, + 3 + ], + [ + "LC_FIFO_TIMEOUT_ENA", + 11, + 1 + ] + ] + }, + { + "name": "CVSD_CONF0", + "offset": "0x80", + "reset": "0x80007FFF", + "fields": [ + [ + "CVSD_Y_MAX", + 0, + 16 + ], + [ + "CVSD_Y_MIN", + 16, + 16 + ] + ] + }, + { + "name": "CVSD_CONF1", + "offset": "0x84", + "reset": "0x000A0500", + "fields": [ + [ + "CVSD_SIGMA_MAX", + 0, + 16 + ], + [ + "CVSD_SIGMA_MIN", + 16, + 16 + ] + ] + }, + { + "name": "CVSD_CONF2", + "offset": "0x88", + "reset": "0x000502A4", + "fields": [ + [ + "CVSD_K", + 0, + 3 + ], + [ + "CVSD_J", + 3, + 3 + ], + [ + "CVSD_BETA", + 6, + 10 + ], + [ + "CVSD_H", + 16, + 3 + ] + ] + }, + { + "name": "PLC_CONF0", + "offset": "0x8C", + "reset": "0x08A80339", + "fields": [ + [ + "GOOD_PACK_MAX", + 0, + 6 + ], + [ + "N_ERR_SEG", + 6, + 3 + ], + [ + "SHIFT_RATE", + 9, + 3 + ], + [ + "MAX_SLIDE_SAMPLE", + 12, + 8 + ], + [ + "PACK_LEN_8K", + 20, + 5 + ], + [ + "N_MIN_ERR", + 25, + 3 + ] + ] + }, + { + "name": "PLC_CONF1", + "offset": "0x90", + "reset": "0xA0178A05", + "fields": [ + [ + "BAD_CEF_ATTEN_PARA", + 0, + 8 + ], + [ + "BAD_CEF_ATTEN_PARA_SHIFT", + 8, + 4 + ], + [ + "BAD_OLA_WIN2_PARA_SHIFT", + 12, + 4 + ], + [ + "BAD_OLA_WIN2_PARA", + 16, + 8 + ], + [ + "SLIDE_WIN_LEN", + 24, + 8 + ] + ] + }, + { + "name": "PLC_CONF2", + "offset": "0x94", + "reset": "0x00000028", + "fields": [ + [ + "CVSD_SEG_MOD", + 0, + 2 + ], + [ + "MIN_PERIOD", + 2, + 5 + ] + ] + }, + { + "name": "ESCO_CONF0", + "offset": "0x98", + "fields": [ + [ + "ESCO_EN", + 0, + 1 + ], + [ + "ESCO_CHAN_MOD", + 1, + 1 + ], + [ + "ESCO_CVSD_DEC_PACK_ERR", + 2, + 1 + ], + [ + "ESCO_CVSD_PACK_LEN_8K", + 3, + 5 + ], + [ + "ESCO_CVSD_INF_EN", + 8, + 1 + ], + [ + "CVSD_DEC_START", + 9, + 1 + ], + [ + "CVSD_DEC_RESET", + 10, + 1 + ], + [ + "PLC_EN", + 11, + 1 + ], + [ + "PLC2DMA_EN", + 12, + 1 + ] + ] + }, + { + "name": "SCO_CONF0", + "offset": "0x9C", + "fields": [ + [ + "SCO_WITH_I2S_EN", + 0, + 1 + ], + [ + "SCO_NO_I2S_EN", + 1, + 1 + ], + [ + "CVSD_ENC_START", + 2, + 1 + ], + [ + "CVSD_ENC_RESET", + 3, + 1 + ] + ] + }, + { + "name": "CONF1", + "offset": "0xA0", + "reset": "0x00000089", + "fields": [ + [ + "TX_PCM_CONF", + 0, + 3 + ], + [ + "TX_PCM_BYPASS", + 3, + 1 + ], + [ + "RX_PCM_CONF", + 4, + 3 + ], + [ + "RX_PCM_BYPASS", + 7, + 1 + ], + [ + "TX_STOP_EN", + 8, + 1 + ], + [ + "TX_ZEROS_RM_EN", + 9, + 1 + ] + ] + }, + { + "name": "PD_CONF", + "offset": "0xA4", + "reset": "0x0000000A", + "fields": [ + [ + "FIFO_FORCE_PD", + 0, + 1 + ], + [ + "FIFO_FORCE_PU", + 1, + 1 + ], + [ + "PLC_MEM_FORCE_PD", + 2, + 1 + ], + [ + "PLC_MEM_FORCE_PU", + 3, + 1 + ] + ] + }, + { + "name": "CONF2", + "offset": "0xA8", + "fields": [ + [ + "CAMERA_EN", + 0, + 1 + ], + [ + "LCD_TX_WRX2_EN", + 1, + 1 + ], + [ + "LCD_TX_SDX2_EN", + 2, + 1 + ], + [ + "DATA_ENABLE_TEST_EN", + 3, + 1 + ], + [ + "DATA_ENABLE", + 4, + 1 + ], + [ + "LCD_EN", + 5, + 1 + ], + [ + "EXT_ADC_START_EN", + 6, + 1 + ], + [ + "INTER_VALID_EN", + 7, + 1 + ] + ] + }, + { + "name": "CLKM_CONF", + "offset": "0xAC", + "reset": "0x00000004", + "fields": [ + [ + "CLKM_DIV_NUM", + 0, + 8 + ], + [ + "CLKM_DIV_B", + 8, + 6 + ], + [ + "CLKM_DIV_A", + 14, + 6 + ], + [ + "CLK_EN", + 20, + 1 + ], + [ + "CLKA_ENA", + 21, + 1 + ] + ] + }, + { + "name": "SAMPLE_RATE_CONF", + "offset": "0xB0", + "reset": "0x00410186", + "fields": [ + [ + "TX_BCK_DIV_NUM", + 0, + 6 + ], + [ + "RX_BCK_DIV_NUM", + 6, + 6 + ], + [ + "TX_BITS_MOD", + 12, + 6 + ], + [ + "RX_BITS_MOD", + 18, + 6 + ] + ] + }, + { + "name": "PDM_CONF", + "offset": "0xB4", + "reset": "0x01550020", + "fields": [ + [ + "TX_PDM_EN", + 0, + 1 + ], + [ + "RX_PDM_EN", + 1, + 1 + ], + [ + "PCM2PDM_CONV_EN", + 2, + 1 + ], + [ + "PDM2PCM_CONV_EN", + 3, + 1 + ], + [ + "TX_PDM_SINC_OSR2", + 4, + 4 + ], + [ + "TX_PDM_PRESCALE", + 8, + 8 + ], + [ + "TX_PDM_HP_IN_SHIFT", + 16, + 2 + ], + [ + "TX_PDM_LP_IN_SHIFT", + 18, + 2 + ], + [ + "TX_PDM_SINC_IN_SHIFT", + 20, + 2 + ], + [ + "TX_PDM_SIGMADELTA_IN_SHIFT", + 22, + 2 + ], + [ + "RX_PDM_SINC_DSR_16_EN", + 24, + 1 + ], + [ + "TX_PDM_HP_BYPASS", + 25, + 1 + ] + ] + }, + { + "name": "PDM_FREQ_CONF", + "offset": "0xB8", + "reset": "0x000F01E0", + "fields": [ + [ + "TX_PDM_FS", + 0, + 10 + ], + [ + "TX_PDM_FP", + 10, + 10 + ] + ] + }, + { + "name": "STATE", + "offset": "0xBC", + "reset": "0x00000007", + "fields": [ + [ + "TX_IDLE", + 0, + 1, + "r" + ], + [ + "TX_FIFO_RESET_BACK", + 1, + 1, + "r" + ], + [ + "RX_FIFO_RESET_BACK", + 2, + 1, + "r" + ] + ] + }, + { + "name": "DATE", + "offset": "0xFC", + "reset": "0x01604201", + "fields": [ + [ + "I2SDATE", + 0, + 32 + ] + ] + } + ] + }, + { + "name": "UART1", + "base": "0x3FF50000", + "interrupts": [ + "UART1" + ], + "derived_from": "UART0" + }, + { + "name": "I2C0", + "base": "0x3FF53000", + "interrupts": [ + "I2C_EXT0" + ], + "registers": [ + { + "name": "SCL_LOW_PERIOD", + "offset": "0x00", + "fields": [ + [ + "SCL_LOW_PERIOD", + 0, + 14 + ] + ] + }, + { + "name": "CTR", + "offset": "0x04", + "reset": "0x00000003", + "fields": [ + [ + "SDA_FORCE_OUT", + 0, + 1 + ], + [ + "SCL_FORCE_OUT", + 1, + 1 + ], + [ + "SAMPLE_SCL_LEVEL", + 2, + 1 + ], + [ + "MS_MODE", + 4, + 1 + ], + [ + "TRANS_START", + 5, + 1 + ], + [ + "TX_LSB_FIRST", + 6, + 1 + ], + [ + "RX_LSB_FIRST", + 7, + 1 + ], + [ + "CLK_EN", + 8, + 1 + ] + ] + }, + { + "name": "SR", + "offset": "0x08", + "fields": [ + [ + "ACK_REC", + 0, + 1, + "r" + ], + [ + "SLAVE_RW", + 1, + 1, + "r" + ], + [ + "TIME_OUT", + 2, + 1, + "r" + ], + [ + "ARB_LOST", + 3, + 1, + "r" + ], + [ + "BUS_BUSY", + 4, + 1, + "r" + ], + [ + "SLAVE_ADDRESSED", + 5, + 1, + "r" + ], + [ + "BYTE_TRANS", + 6, + 1, + "r" + ], + [ + "RXFIFO_CNT", + 8, + 6, + "r" + ], + [ + "TXFIFO_CNT", + 18, + 6, + "r" + ], + [ + "SCL_MAIN_STATE_LAST", + 24, + 3, + "r" + ], + [ + "SCL_STATE_LAST", + 28, + 3, + "r" + ] + ] + }, + { + "name": "TO", + "offset": "0x0C", + "fields": [ + [ + "TIME_OUT", + 0, + 20 + ] + ] + }, + { + "name": "SLAVE_ADDR", + "offset": "0x10", + "fields": [ + [ + "SLAVE_ADDR", + 0, + 15 + ], + [ + "ADDR_10BIT_EN", + 31, + 1 + ] + ] + }, + { + "name": "RXFIFO_ST", + "offset": "0x14", + "fields": [ + [ + "RXFIFO_START_ADDR", + 0, + 5, + "r" + ], + [ + "RXFIFO_END_ADDR", + 5, + 5, + "r" + ], + [ + "TXFIFO_START_ADDR", + 10, + 5, + "r" + ], + [ + "TXFIFO_END_ADDR", + 15, + 5, + "r" + ] + ] + }, + { + "name": "FIFO_CONF", + "offset": "0x18", + "reset": "0x0155408B", + "fields": [ + [ + "RXFIFO_FULL_THRHD", + 0, + 5 + ], + [ + "TXFIFO_EMPTY_THRHD", + 5, + 5 + ], + [ + "NONFIFO_EN", + 10, + 1 + ], + [ + "FIFO_ADDR_CFG_EN", + 11, + 1 + ], + [ + "RX_FIFO_RST", + 12, + 1 + ], + [ + "TX_FIFO_RST", + 13, + 1 + ], + [ + "NONFIFO_RX_THRES", + 14, + 6 + ], + [ + "NONFIFO_TX_THRES", + 20, + 6 + ] + ] + }, + { + "name": "DATA", + "offset": "0x1C", + "fields": [ + [ + "FIFO_RDATA", + 0, + 8, + "r" + ] + ] + }, + { + "name": "INT_RAW", + "offset": "0x20", + "fields": [ + [ + "RXFIFO_FULL_INT_RAW", + 0, + 1, + "r" + ], + [ + "TXFIFO_EMPTY_INT_RAW", + 1, + 1, + "r" + ], + [ + "RXFIFO_OVF_INT_RAW", + 2, + 1, + "r" + ], + [ + "END_DETECT_INT_RAW", + 3, + 1, + "r" + ], + [ + "SLAVE_TRAN_COMP_INT_RAW", + 4, + 1, + "r" + ], + [ + "ARBITRATION_LOST_INT_RAW", + 5, + 1, + "r" + ], + [ + "MASTER_TRAN_COMP_INT_RAW", + 6, + 1, + "r" + ], + [ + "TRANS_COMPLETE_INT_RAW", + 7, + 1, + "r" + ], + [ + "TIME_OUT_INT_RAW", + 8, + 1, + "r" + ], + [ + "TRANS_START_INT_RAW", + 9, + 1, + "r" + ], + [ + "ACK_ERR_INT_RAW", + 10, + 1, + "r" + ], + [ + "RX_REC_FULL_INT_RAW", + 11, + 1, + "r" + ], + [ + "TX_SEND_EMPTY_INT_RAW", + 12, + 1, + "r" + ] + ] + }, + { + "name": "INT_CLR", + "offset": "0x24", + "fields": [ + [ + "RXFIFO_FULL_INT_CLR", + 0, + 1, + "w" + ], + [ + "TXFIFO_EMPTY_INT_CLR", + 1, + 1, + "w" + ], + [ + "RXFIFO_OVF_INT_CLR", + 2, + 1, + "w" + ], + [ + "END_DETECT_INT_CLR", + 3, + 1, + "w" + ], + [ + "SLAVE_TRAN_COMP_INT_CLR", + 4, + 1, + "w" + ], + [ + "ARBITRATION_LOST_INT_CLR", + 5, + 1, + "w" + ], + [ + "MASTER_TRAN_COMP_INT_CLR", + 6, + 1, + "w" + ], + [ + "TRANS_COMPLETE_INT_CLR", + 7, + 1, + "w" + ], + [ + "TIME_OUT_INT_CLR", + 8, + 1, + "w" + ], + [ + "TRANS_START_INT_CLR", + 9, + 1, + "w" + ], + [ + "ACK_ERR_INT_CLR", + 10, + 1, + "w" + ], + [ + "RX_REC_FULL_INT_CLR", + 11, + 1, + "w" + ], + [ + "TX_SEND_EMPTY_INT_CLR", + 12, + 1, + "w" + ] + ] + }, + { + "name": "INT_ENA", + "offset": "0x28", + "fields": [ + [ + "RXFIFO_FULL_INT_ENA", + 0, + 1 + ], + [ + "TXFIFO_EMPTY_INT_ENA", + 1, + 1 + ], + [ + "RXFIFO_OVF_INT_ENA", + 2, + 1 + ], + [ + "END_DETECT_INT_ENA", + 3, + 1 + ], + [ + "SLAVE_TRAN_COMP_INT_ENA", + 4, + 1 + ], + [ + "ARBITRATION_LOST_INT_ENA", + 5, + 1 + ], + [ + "MASTER_TRAN_COMP_INT_ENA", + 6, + 1 + ], + [ + "TRANS_COMPLETE_INT_ENA", + 7, + 1 + ], + [ + "TIME_OUT_INT_ENA", + 8, + 1 + ], + [ + "TRANS_START_INT_ENA", + 9, + 1 + ], + [ + "ACK_ERR_INT_ENA", + 10, + 1 + ], + [ + "RX_REC_FULL_INT_ENA", + 11, + 1 + ], + [ + "TX_SEND_EMPTY_INT_ENA", + 12, + 1 + ] + ] + }, + { + "name": "INT_STATUS", + "offset": "0x2C", + "fields": [ + [ + "RXFIFO_FULL_INT_ST", + 0, + 1, + "r" + ], + [ + "TXFIFO_EMPTY_INT_ST", + 1, + 1, + "r" + ], + [ + "RXFIFO_OVF_INT_ST", + 2, + 1, + "r" + ], + [ + "END_DETECT_INT_ST", + 3, + 1, + "r" + ], + [ + "SLAVE_TRAN_COMP_INT_ST", + 4, + 1, + "r" + ], + [ + "ARBITRATION_LOST_INT_ST", + 5, + 1, + "r" + ], + [ + "MASTER_TRAN_COMP_INT_ST", + 6, + 1, + "r" + ], + [ + "TRANS_COMPLETE_INT_ST", + 7, + 1, + "r" + ], + [ + "TIME_OUT_INT_ST", + 8, + 1, + "r" + ], + [ + "TRANS_START_INT_ST", + 9, + 1, + "r" + ], + [ + "ACK_ERR_INT_ST", + 10, + 1, + "r" + ], + [ + "RX_REC_FULL_INT_ST", + 11, + 1, + "r" + ], + [ + "TX_SEND_EMPTY_INT_ST", + 12, + 1, + "r" + ] + ] + }, + { + "name": "SDA_HOLD", + "offset": "0x30", + "fields": [ + [ + "TIME", + 0, + 10 + ] + ] + }, + { + "name": "SDA_SAMPLE", + "offset": "0x34", + "fields": [ + [ + "TIME", + 0, + 10 + ] + ] + }, + { + "name": "SCL_HIGH_PERIOD", + "offset": "0x38", + "fields": [ + [ + "SCL_HIGH_PERIOD", + 0, + 14 + ] + ] + }, + { + "name": "SCL_START_HOLD", + "offset": "0x40", + "reset": "0x00000008", + "fields": [ + [ + "TIME", + 0, + 10 + ] + ] + }, + { + "name": "SCL_RSTART_SETUP", + "offset": "0x44", + "reset": "0x00000008", + "fields": [ + [ + "TIME", + 0, + 10 + ] + ] + }, + { + "name": "SCL_STOP_HOLD", + "offset": "0x48", + "fields": [ + [ + "TIME", + 0, + 14 + ] + ] + }, + { + "name": "SCL_STOP_SETUP", + "offset": "0x4C", + "fields": [ + [ + "TIME", + 0, + 10 + ] + ] + }, + { + "name": "SCL_FILTER_CFG", + "offset": "0x50", + "reset": "0x00000008", + "fields": [ + [ + "SCL_FILTER_THRES", + 0, + 3 + ], + [ + "SCL_FILTER_EN", + 3, + 1 + ] + ] + }, + { + "name": "SDA_FILTER_CFG", + "offset": "0x54", + "reset": "0x00000008", + "fields": [ + [ + "SDA_FILTER_THRES", + 0, + 3 + ], + [ + "SDA_FILTER_EN", + 3, + 1 + ] + ] + }, + { + "name": "COMD0", + "offset": "0x58", + "fields": [ + [ + "COMMAND", + 0, + 14 + ], + [ + "COMMAND_DONE", + 31, + 1 + ] + ] + }, + { + "name": "COMD1", + "offset": "0x5C", + "fields": [ + [ + "COMMAND", + 0, + 14 + ], + [ + "COMMAND_DONE", + 31, + 1 + ] + ] + }, + { + "name": "COMD2", + "offset": "0x60", + "fields": [ + [ + "COMMAND", + 0, + 14 + ], + [ + "COMMAND_DONE", + 31, + 1 + ] + ] + }, + { + "name": "COMD3", + "offset": "0x64", + "fields": [ + [ + "COMMAND", + 0, + 14 + ], + [ + "COMMAND_DONE", + 31, + 1 + ] + ] + }, + { + "name": "COMD4", + "offset": "0x68", + "fields": [ + [ + "COMMAND", + 0, + 14 + ], + [ + "COMMAND_DONE", + 31, + 1 + ] + ] + }, + { + "name": "COMD5", + "offset": "0x6C", + "fields": [ + [ + "COMMAND", + 0, + 14 + ], + [ + "COMMAND_DONE", + 31, + 1 + ] + ] + }, + { + "name": "COMD6", + "offset": "0x70", + "fields": [ + [ + "COMMAND", + 0, + 14 + ], + [ + "COMMAND_DONE", + 31, + 1 + ] + ] + }, + { + "name": "COMD7", + "offset": "0x74", + "fields": [ + [ + "COMMAND", + 0, + 14 + ], + [ + "COMMAND_DONE", + 31, + 1 + ] + ] + }, + { + "name": "COMD8", + "offset": "0x78", + "fields": [ + [ + "COMMAND", + 0, + 14 + ], + [ + "COMMAND_DONE", + 31, + 1 + ] + ] + }, + { + "name": "COMD9", + "offset": "0x7C", + "fields": [ + [ + "COMMAND", + 0, + 14 + ], + [ + "COMMAND_DONE", + 31, + 1 + ] + ] + }, + { + "name": "COMD10", + "offset": "0x80", + "fields": [ + [ + "COMMAND", + 0, + 14 + ], + [ + "COMMAND_DONE", + 31, + 1 + ] + ] + }, + { + "name": "COMD11", + "offset": "0x84", + "fields": [ + [ + "COMMAND", + 0, + 14 + ], + [ + "COMMAND_DONE", + 31, + 1 + ] + ] + }, + { + "name": "COMD12", + "offset": "0x88", + "fields": [ + [ + "COMMAND", + 0, + 14 + ], + [ + "COMMAND_DONE", + 31, + 1 + ] + ] + }, + { + "name": "COMD13", + "offset": "0x8C", + "fields": [ + [ + "COMMAND", + 0, + 14 + ], + [ + "COMMAND_DONE", + 31, + 1 + ] + ] + }, + { + "name": "COMD14", + "offset": "0x90", + "fields": [ + [ + "COMMAND", + 0, + 14 + ], + [ + "COMMAND_DONE", + 31, + 1 + ] + ] + }, + { + "name": "COMD15", + "offset": "0x94", + "fields": [ + [ + "COMMAND", + 0, + 14 + ], + [ + "COMMAND_DONE", + 31, + 1 + ] + ] + }, + { + "name": "DATE", + "offset": "0xF8", + "reset": "0x16042000", + "fields": [ + [ + "DATE", + 0, + 32 + ] + ] + }, + { + "name": "FIFO_START_ADDR", + "offset": "0x100" + } + ] + }, + { + "name": "UHCI0", + "base": "0x3FF54000", + "interrupts": [ + "UHCI0" + ], + "registers": [ + { + "name": "CONF0", + "offset": "0x00", + "reset": "0x00370100", + "fields": [ + [ + "IN_RST", + 0, + 1 + ], + [ + "OUT_RST", + 1, + 1 + ], + [ + "AHBM_FIFO_RST", + 2, + 1 + ], + [ + "AHBM_RST", + 3, + 1 + ], + [ + "IN_LOOP_TEST", + 4, + 1 + ], + [ + "OUT_LOOP_TEST", + 5, + 1 + ], + [ + "OUT_AUTO_WRBACK", + 6, + 1 + ], + [ + "OUT_NO_RESTART_CLR", + 7, + 1 + ], + [ + "OUT_EOF_MODE", + 8, + 1 + ], + [ + "UART0_CE", + 9, + 1 + ], + [ + "UART1_CE", + 10, + 1 + ], + [ + "UART2_CE", + 11, + 1 + ], + [ + "OUTDSCR_BURST_EN", + 12, + 1 + ], + [ + "INDSCR_BURST_EN", + 13, + 1 + ], + [ + "OUT_DATA_BURST_EN", + 14, + 1 + ], + [ + "MEM_TRANS_EN", + 15, + 1 + ], + [ + "SEPER_EN", + 16, + 1 + ], + [ + "HEAD_EN", + 17, + 1 + ], + [ + "CRC_REC_EN", + 18, + 1 + ], + [ + "UART_IDLE_EOF_EN", + 19, + 1 + ], + [ + "LEN_EOF_EN", + 20, + 1 + ], + [ + "ENCODE_CRC_EN", + 21, + 1 + ], + [ + "CLK_EN", + 22, + 1 + ], + [ + "UART_RX_BRK_EOF_EN", + 23, + 1 + ] + ] + }, + { + "name": "INT_RAW", + "offset": "0x04", + "fields": [ + [ + "RX_START_INT_RAW", + 0, + 1, + "r" + ], + [ + "TX_START_INT_RAW", + 1, + 1, + "r" + ], + [ + "RX_HUNG_INT_RAW", + 2, + 1, + "r" + ], + [ + "TX_HUNG_INT_RAW", + 3, + 1, + "r" + ], + [ + "IN_DONE_INT_RAW", + 4, + 1, + "r" + ], + [ + "IN_SUC_EOF_INT_RAW", + 5, + 1, + "r" + ], + [ + "IN_ERR_EOF_INT_RAW", + 6, + 1, + "r" + ], + [ + "OUT_DONE_INT_RAW", + 7, + 1, + "r" + ], + [ + "OUT_EOF_INT_RAW", + 8, + 1, + "r" + ], + [ + "IN_DSCR_ERR_INT_RAW", + 9, + 1, + "r" + ], + [ + "OUT_DSCR_ERR_INT_RAW", + 10, + 1, + "r" + ], + [ + "IN_DSCR_EMPTY_INT_RAW", + 11, + 1, + "r" + ], + [ + "OUTLINK_EOF_ERR_INT_RAW", + 12, + 1, + "r" + ], + [ + "OUT_TOTAL_EOF_INT_RAW", + 13, + 1, + "r" + ], + [ + "SEND_S_Q_INT_RAW", + 14, + 1, + "r" + ], + [ + "SEND_A_Q_INT_RAW", + 15, + 1, + "r" + ], + [ + "DMA_INFIFO_FULL_WM_INT_RAW", + 16, + 1, + "r" + ] + ] + }, + { + "name": "INT_ST", + "offset": "0x08", + "fields": [ + [ + "RX_START_INT_ST", + 0, + 1, + "r" + ], + [ + "TX_START_INT_ST", + 1, + 1, + "r" + ], + [ + "RX_HUNG_INT_ST", + 2, + 1, + "r" + ], + [ + "TX_HUNG_INT_ST", + 3, + 1, + "r" + ], + [ + "IN_DONE_INT_ST", + 4, + 1, + "r" + ], + [ + "IN_SUC_EOF_INT_ST", + 5, + 1, + "r" + ], + [ + "IN_ERR_EOF_INT_ST", + 6, + 1, + "r" + ], + [ + "OUT_DONE_INT_ST", + 7, + 1, + "r" + ], + [ + "OUT_EOF_INT_ST", + 8, + 1, + "r" + ], + [ + "IN_DSCR_ERR_INT_ST", + 9, + 1, + "r" + ], + [ + "OUT_DSCR_ERR_INT_ST", + 10, + 1, + "r" + ], + [ + "IN_DSCR_EMPTY_INT_ST", + 11, + 1, + "r" + ], + [ + "OUTLINK_EOF_ERR_INT_ST", + 12, + 1, + "r" + ], + [ + "OUT_TOTAL_EOF_INT_ST", + 13, + 1, + "r" + ], + [ + "SEND_S_Q_INT_ST", + 14, + 1, + "r" + ], + [ + "SEND_A_Q_INT_ST", + 15, + 1, + "r" + ], + [ + "DMA_INFIFO_FULL_WM_INT_ST", + 16, + 1, + "r" + ] + ] + }, + { + "name": "INT_ENA", + "offset": "0x0C", + "fields": [ + [ + "RX_START_INT_ENA", + 0, + 1 + ], + [ + "TX_START_INT_ENA", + 1, + 1 + ], + [ + "RX_HUNG_INT_ENA", + 2, + 1 + ], + [ + "TX_HUNG_INT_ENA", + 3, + 1 + ], + [ + "IN_DONE_INT_ENA", + 4, + 1 + ], + [ + "IN_SUC_EOF_INT_ENA", + 5, + 1 + ], + [ + "IN_ERR_EOF_INT_ENA", + 6, + 1 + ], + [ + "OUT_DONE_INT_ENA", + 7, + 1 + ], + [ + "OUT_EOF_INT_ENA", + 8, + 1 + ], + [ + "IN_DSCR_ERR_INT_ENA", + 9, + 1 + ], + [ + "OUT_DSCR_ERR_INT_ENA", + 10, + 1 + ], + [ + "IN_DSCR_EMPTY_INT_ENA", + 11, + 1 + ], + [ + "OUTLINK_EOF_ERR_INT_ENA", + 12, + 1 + ], + [ + "OUT_TOTAL_EOF_INT_ENA", + 13, + 1 + ], + [ + "SEND_S_Q_INT_ENA", + 14, + 1 + ], + [ + "SEND_A_Q_INT_ENA", + 15, + 1 + ], + [ + "DMA_INFIFO_FULL_WM_INT_ENA", + 16, + 1 + ] + ] + }, + { + "name": "INT_CLR", + "offset": "0x10", + "fields": [ + [ + "RX_START_INT_CLR", + 0, + 1, + "w" + ], + [ + "TX_START_INT_CLR", + 1, + 1, + "w" + ], + [ + "RX_HUNG_INT_CLR", + 2, + 1, + "w" + ], + [ + "TX_HUNG_INT_CLR", + 3, + 1, + "w" + ], + [ + "IN_DONE_INT_CLR", + 4, + 1, + "w" + ], + [ + "IN_SUC_EOF_INT_CLR", + 5, + 1, + "w" + ], + [ + "IN_ERR_EOF_INT_CLR", + 6, + 1, + "w" + ], + [ + "OUT_DONE_INT_CLR", + 7, + 1, + "w" + ], + [ + "OUT_EOF_INT_CLR", + 8, + 1, + "w" + ], + [ + "IN_DSCR_ERR_INT_CLR", + 9, + 1, + "w" + ], + [ + "OUT_DSCR_ERR_INT_CLR", + 10, + 1, + "w" + ], + [ + "IN_DSCR_EMPTY_INT_CLR", + 11, + 1, + "w" + ], + [ + "OUTLINK_EOF_ERR_INT_CLR", + 12, + 1, + "w" + ], + [ + "OUT_TOTAL_EOF_INT_CLR", + 13, + 1, + "w" + ], + [ + "SEND_S_Q_INT_CLR", + 14, + 1, + "w" + ], + [ + "SEND_A_Q_INT_CLR", + 15, + 1, + "w" + ], + [ + "DMA_INFIFO_FULL_WM_INT_CLR", + 16, + 1, + "w" + ] + ] + }, + { + "name": "DMA_OUT_STATUS", + "offset": "0x14", + "reset": "0x00000002", + "fields": [ + [ + "OUT_FULL", + 0, + 1, + "r" + ], + [ + "OUT_EMPTY", + 1, + 1, + "r" + ] + ] + }, + { + "name": "DMA_OUT_PUSH", + "offset": "0x18", + "fields": [ + [ + "OUTFIFO_WDATA", + 0, + 9 + ], + [ + "OUTFIFO_PUSH", + 16, + 1 + ] + ] + }, + { + "name": "DMA_IN_STATUS", + "offset": "0x1C", + "reset": "0x00000002", + "fields": [ + [ + "IN_FULL", + 0, + 1, + "r" + ], + [ + "IN_EMPTY", + 1, + 1, + "r" + ], + [ + "RX_ERR_CAUSE", + 4, + 3, + "r" + ] + ] + }, + { + "name": "DMA_IN_POP", + "offset": "0x20", + "fields": [ + [ + "INFIFO_RDATA", + 0, + 12, + "r" + ], + [ + "INFIFO_POP", + 16, + 1 + ] + ] + }, + { + "name": "DMA_OUT_LINK", + "offset": "0x24", + "fields": [ + [ + "OUTLINK_ADDR", + 0, + 20 + ], + [ + "OUTLINK_STOP", + 28, + 1 + ], + [ + "OUTLINK_START", + 29, + 1 + ], + [ + "OUTLINK_RESTART", + 30, + 1 + ], + [ + "OUTLINK_PARK", + 31, + 1, + "r" + ] + ] + }, + { + "name": "DMA_IN_LINK", + "offset": "0x28", + "reset": "0x00100000", + "fields": [ + [ + "INLINK_ADDR", + 0, + 20 + ], + [ + "INLINK_AUTO_RET", + 20, + 1 + ], + [ + "INLINK_STOP", + 28, + 1 + ], + [ + "INLINK_START", + 29, + 1 + ], + [ + "INLINK_RESTART", + 30, + 1 + ], + [ + "INLINK_PARK", + 31, + 1, + "r" + ] + ] + }, + { + "name": "CONF1", + "offset": "0x2C", + "reset": "0x00000033", + "fields": [ + [ + "CHECK_SUM_EN", + 0, + 1 + ], + [ + "CHECK_SEQ_EN", + 1, + 1 + ], + [ + "CRC_DISABLE", + 2, + 1 + ], + [ + "SAVE_HEAD", + 3, + 1 + ], + [ + "TX_CHECK_SUM_RE", + 4, + 1 + ], + [ + "TX_ACK_NUM_RE", + 5, + 1 + ], + [ + "CHECK_OWNER", + 6, + 1 + ], + [ + "WAIT_SW_START", + 7, + 1 + ], + [ + "SW_START", + 8, + 1 + ], + [ + "DMA_INFIFO_FULL_THRS", + 9, + 12 + ] + ] + }, + { + "name": "STATE0", + "offset": "0x30", + "fields": [ + [ + "STATE0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "STATE1", + "offset": "0x34", + "fields": [ + [ + "STATE1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DMA_OUT_EOF_DES_ADDR", + "offset": "0x38", + "fields": [ + [ + "OUT_EOF_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DMA_IN_SUC_EOF_DES_ADDR", + "offset": "0x3C", + "fields": [ + [ + "IN_SUC_EOF_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DMA_IN_ERR_EOF_DES_ADDR", + "offset": "0x40", + "fields": [ + [ + "IN_ERR_EOF_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DMA_OUT_EOF_BFR_DES_ADDR", + "offset": "0x44", + "fields": [ + [ + "OUT_EOF_BFR_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "AHB_TEST", + "offset": "0x48", + "fields": [ + [ + "AHB_TESTMODE", + 0, + 3 + ], + [ + "AHB_TESTADDR", + 4, + 2 + ] + ] + }, + { + "name": "DMA_IN_DSCR", + "offset": "0x4C", + "fields": [ + [ + "INLINK_DSCR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DMA_IN_DSCR_BF0", + "offset": "0x50", + "fields": [ + [ + "INLINK_DSCR_BF0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DMA_IN_DSCR_BF1", + "offset": "0x54", + "fields": [ + [ + "INLINK_DSCR_BF1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DMA_OUT_DSCR", + "offset": "0x58", + "fields": [ + [ + "OUTLINK_DSCR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DMA_OUT_DSCR_BF0", + "offset": "0x5C", + "fields": [ + [ + "OUTLINK_DSCR_BF0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DMA_OUT_DSCR_BF1", + "offset": "0x60", + "fields": [ + [ + "OUTLINK_DSCR_BF1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "ESCAPE_CONF", + "offset": "0x64", + "reset": "0x00000033", + "fields": [ + [ + "TX_C0_ESC_EN", + 0, + 1 + ], + [ + "TX_DB_ESC_EN", + 1, + 1 + ], + [ + "TX_11_ESC_EN", + 2, + 1 + ], + [ + "TX_13_ESC_EN", + 3, + 1 + ], + [ + "RX_C0_ESC_EN", + 4, + 1 + ], + [ + "RX_DB_ESC_EN", + 5, + 1 + ], + [ + "RX_11_ESC_EN", + 6, + 1 + ], + [ + "RX_13_ESC_EN", + 7, + 1 + ] + ] + }, + { + "name": "HUNG_CONF", + "offset": "0x68", + "reset": "0x00810810", + "fields": [ + [ + "TXFIFO_TIMEOUT", + 0, + 8 + ], + [ + "TXFIFO_TIMEOUT_SHIFT", + 8, + 3 + ], + [ + "TXFIFO_TIMEOUT_ENA", + 11, + 1 + ], + [ + "RXFIFO_TIMEOUT", + 12, + 8 + ], + [ + "RXFIFO_TIMEOUT_SHIFT", + 20, + 3 + ], + [ + "RXFIFO_TIMEOUT_ENA", + 23, + 1 + ] + ] + }, + { + "name": "ACK_NUM", + "offset": "0x6C" + }, + { + "name": "RX_HEAD", + "offset": "0x70", + "fields": [ + [ + "RX_HEAD", + 0, + 32, + "r" + ] + ] + }, + { + "name": "QUICK_SENT", + "offset": "0x74", + "fields": [ + [ + "SINGLE_SEND_NUM", + 0, + 3 + ], + [ + "SINGLE_SEND_EN", + 3, + 1 + ], + [ + "ALWAYS_SEND_NUM", + 4, + 3 + ], + [ + "ALWAYS_SEND_EN", + 7, + 1 + ] + ] + }, + { + "name": "Q0_WORD0", + "offset": "0x78", + "fields": [ + [ + "SEND_Q0_WORD0", + 0, + 32 + ] + ] + }, + { + "name": "Q0_WORD1", + "offset": "0x7C", + "fields": [ + [ + "SEND_Q0_WORD1", + 0, + 32 + ] + ] + }, + { + "name": "Q1_WORD0", + "offset": "0x80", + "fields": [ + [ + "SEND_Q1_WORD0", + 0, + 32 + ] + ] + }, + { + "name": "Q1_WORD1", + "offset": "0x84", + "fields": [ + [ + "SEND_Q1_WORD1", + 0, + 32 + ] + ] + }, + { + "name": "Q2_WORD0", + "offset": "0x88", + "fields": [ + [ + "SEND_Q2_WORD0", + 0, + 32 + ] + ] + }, + { + "name": "Q2_WORD1", + "offset": "0x8C", + "fields": [ + [ + "SEND_Q2_WORD1", + 0, + 32 + ] + ] + }, + { + "name": "Q3_WORD0", + "offset": "0x90", + "fields": [ + [ + "SEND_Q3_WORD0", + 0, + 32 + ] + ] + }, + { + "name": "Q3_WORD1", + "offset": "0x94", + "fields": [ + [ + "SEND_Q3_WORD1", + 0, + 32 + ] + ] + }, + { + "name": "Q4_WORD0", + "offset": "0x98", + "fields": [ + [ + "SEND_Q4_WORD0", + 0, + 32 + ] + ] + }, + { + "name": "Q4_WORD1", + "offset": "0x9C", + "fields": [ + [ + "SEND_Q4_WORD1", + 0, + 32 + ] + ] + }, + { + "name": "Q5_WORD0", + "offset": "0xA0", + "fields": [ + [ + "SEND_Q5_WORD0", + 0, + 32 + ] + ] + }, + { + "name": "Q5_WORD1", + "offset": "0xA4", + "fields": [ + [ + "SEND_Q5_WORD1", + 0, + 32 + ] + ] + }, + { + "name": "Q6_WORD0", + "offset": "0xA8", + "fields": [ + [ + "SEND_Q6_WORD0", + 0, + 32 + ] + ] + }, + { + "name": "Q6_WORD1", + "offset": "0xAC", + "fields": [ + [ + "SEND_Q6_WORD1", + 0, + 32 + ] + ] + }, + { + "name": "ESC_CONF0", + "offset": "0xB0", + "reset": "0x00DCDBC0", + "fields": [ + [ + "SEPER_CHAR", + 0, + 8 + ], + [ + "SEPER_ESC_CHAR0", + 8, + 8 + ], + [ + "SEPER_ESC_CHAR1", + 16, + 8 + ] + ] + }, + { + "name": "ESC_CONF1", + "offset": "0xB4", + "reset": "0x00DDDBDB", + "fields": [ + [ + "ESC_SEQ0", + 0, + 8 + ], + [ + "ESC_SEQ0_CHAR0", + 8, + 8 + ], + [ + "ESC_SEQ0_CHAR1", + 16, + 8 + ] + ] + }, + { + "name": "ESC_CONF2", + "offset": "0xB8", + "reset": "0x00DEDB11", + "fields": [ + [ + "ESC_SEQ1", + 0, + 8 + ], + [ + "ESC_SEQ1_CHAR0", + 8, + 8 + ], + [ + "ESC_SEQ1_CHAR1", + 16, + 8 + ] + ] + }, + { + "name": "ESC_CONF3", + "offset": "0xBC", + "reset": "0x00DFDB13", + "fields": [ + [ + "ESC_SEQ2", + 0, + 8 + ], + [ + "ESC_SEQ2_CHAR0", + 8, + 8 + ], + [ + "ESC_SEQ2_CHAR1", + 16, + 8 + ] + ] + }, + { + "name": "PKT_THRES", + "offset": "0xC0", + "reset": "0x00000080", + "fields": [ + [ + "PKT_THRS", + 0, + 13 + ] + ] + }, + { + "name": "DATE", + "offset": "0xFC", + "reset": "0x16041001", + "fields": [ + [ + "DATE", + 0, + 32 + ] + ] + } + ] + }, + { + "name": "SLCHOST", + "base": "0x3FF55000", + "registers": [ + { + "name": "HOST_SLCHOST_FUNC2_0", + "offset": "0x10", + "fields": [ + [ + "HOST_SLC_FUNC2_INT", + 24, + 1 + ] + ] + }, + { + "name": "HOST_SLCHOST_FUNC2_1", + "offset": "0x14", + "fields": [ + [ + "HOST_SLC_FUNC2_INT_EN", + 0, + 1 + ] + ] + }, + { + "name": "HOST_SLCHOST_FUNC2_2", + "offset": "0x20", + "reset": "0x00000001", + "fields": [ + [ + "HOST_SLC_FUNC1_MDSTAT", + 0, + 1 + ] + ] + }, + { + "name": "HOST_SLCHOST_GPIO_STATUS0", + "offset": "0x34", + "fields": [ + [ + "HOST_GPIO_SDIO_INT0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "HOST_SLCHOST_GPIO_STATUS1", + "offset": "0x38", + "fields": [ + [ + "HOST_GPIO_SDIO_INT1", + 0, + 8, + "r" + ] + ] + }, + { + "name": "HOST_SLCHOST_GPIO_IN0", + "offset": "0x3C", + "fields": [ + [ + "HOST_GPIO_SDIO_IN0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "HOST_SLCHOST_GPIO_IN1", + "offset": "0x40", + "fields": [ + [ + "HOST_GPIO_SDIO_IN1", + 0, + 8, + "r" + ] + ] + }, + { + "name": "HOST_SLC0HOST_TOKEN_RDATA", + "offset": "0x44", + "fields": [ + [ + "HOST_SLC0_TOKEN0", + 0, + 12, + "r" + ], + [ + "HOST_SLC0_RX_PF_VALID", + 12, + 1, + "r" + ], + [ + "HOST_HOSTSLC0_TOKEN1", + 16, + 12, + "r" + ], + [ + "HOST_SLC0_RX_PF_EOF", + 28, + 4, + "r" + ] + ] + }, + { + "name": "HOST_SLC0_HOST_PF", + "offset": "0x48", + "fields": [ + [ + "HOST_SLC0_PF_DATA", + 0, + 32, + "r" + ] + ] + }, + { + "name": "HOST_SLC1_HOST_PF", + "offset": "0x4C", + "fields": [ + [ + "HOST_SLC1_PF_DATA", + 0, + 32, + "r" + ] + ] + }, + { + "name": "HOST_SLC0HOST_INT_RAW", + "offset": "0x50", + "fields": [ + [ + "HOST_SLC0_TOHOST_BIT0_INT_RAW", + 0, + 1, + "r" + ], + [ + "HOST_SLC0_TOHOST_BIT1_INT_RAW", + 1, + 1, + "r" + ], + [ + "HOST_SLC0_TOHOST_BIT2_INT_RAW", + 2, + 1, + "r" + ], + [ + "HOST_SLC0_TOHOST_BIT3_INT_RAW", + 3, + 1, + "r" + ], + [ + "HOST_SLC0_TOHOST_BIT4_INT_RAW", + 4, + 1, + "r" + ], + [ + "HOST_SLC0_TOHOST_BIT5_INT_RAW", + 5, + 1, + "r" + ], + [ + "HOST_SLC0_TOHOST_BIT6_INT_RAW", + 6, + 1, + "r" + ], + [ + "HOST_SLC0_TOHOST_BIT7_INT_RAW", + 7, + 1, + "r" + ], + [ + "HOST_SLC0_TOKEN0_1TO0_INT_RAW", + 8, + 1, + "r" + ], + [ + "HOST_SLC0_TOKEN1_1TO0_INT_RAW", + 9, + 1, + "r" + ], + [ + "HOST_SLC0_TOKEN0_0TO1_INT_RAW", + 10, + 1, + "r" + ], + [ + "HOST_SLC0_TOKEN1_0TO1_INT_RAW", + 11, + 1, + "r" + ], + [ + "HOST_SLC0HOST_RX_SOF_INT_RAW", + 12, + 1, + "r" + ], + [ + "HOST_SLC0HOST_RX_EOF_INT_RAW", + 13, + 1, + "r" + ], + [ + "HOST_SLC0HOST_RX_START_INT_RAW", + 14, + 1, + "r" + ], + [ + "HOST_SLC0HOST_TX_START_INT_RAW", + 15, + 1, + "r" + ], + [ + "HOST_SLC0_RX_UDF_INT_RAW", + 16, + 1, + "r" + ], + [ + "HOST_SLC0_TX_OVF_INT_RAW", + 17, + 1, + "r" + ], + [ + "HOST_SLC0_RX_PF_VALID_INT_RAW", + 18, + 1, + "r" + ], + [ + "HOST_SLC0_EXT_BIT0_INT_RAW", + 19, + 1, + "r" + ], + [ + "HOST_SLC0_EXT_BIT1_INT_RAW", + 20, + 1, + "r" + ], + [ + "HOST_SLC0_EXT_BIT2_INT_RAW", + 21, + 1, + "r" + ], + [ + "HOST_SLC0_EXT_BIT3_INT_RAW", + 22, + 1, + "r" + ], + [ + "HOST_SLC0_RX_NEW_PACKET_INT_RAW", + 23, + 1, + "r" + ], + [ + "HOST_SLC0_HOST_RD_RETRY_INT_RAW", + 24, + 1, + "r" + ], + [ + "HOST_GPIO_SDIO_INT_RAW", + 25, + 1, + "r" + ] + ] + }, + { + "name": "HOST_SLC1HOST_INT_RAW", + "offset": "0x54", + "fields": [ + [ + "HOST_SLC1_TOHOST_BIT0_INT_RAW", + 0, + 1, + "r" + ], + [ + "HOST_SLC1_TOHOST_BIT1_INT_RAW", + 1, + 1, + "r" + ], + [ + "HOST_SLC1_TOHOST_BIT2_INT_RAW", + 2, + 1, + "r" + ], + [ + "HOST_SLC1_TOHOST_BIT3_INT_RAW", + 3, + 1, + "r" + ], + [ + "HOST_SLC1_TOHOST_BIT4_INT_RAW", + 4, + 1, + "r" + ], + [ + "HOST_SLC1_TOHOST_BIT5_INT_RAW", + 5, + 1, + "r" + ], + [ + "HOST_SLC1_TOHOST_BIT6_INT_RAW", + 6, + 1, + "r" + ], + [ + "HOST_SLC1_TOHOST_BIT7_INT_RAW", + 7, + 1, + "r" + ], + [ + "HOST_SLC1_TOKEN0_1TO0_INT_RAW", + 8, + 1, + "r" + ], + [ + "HOST_SLC1_TOKEN1_1TO0_INT_RAW", + 9, + 1, + "r" + ], + [ + "HOST_SLC1_TOKEN0_0TO1_INT_RAW", + 10, + 1, + "r" + ], + [ + "HOST_SLC1_TOKEN1_0TO1_INT_RAW", + 11, + 1, + "r" + ], + [ + "HOST_SLC1HOST_RX_SOF_INT_RAW", + 12, + 1, + "r" + ], + [ + "HOST_SLC1HOST_RX_EOF_INT_RAW", + 13, + 1, + "r" + ], + [ + "HOST_SLC1HOST_RX_START_INT_RAW", + 14, + 1, + "r" + ], + [ + "HOST_SLC1HOST_TX_START_INT_RAW", + 15, + 1, + "r" + ], + [ + "HOST_SLC1_RX_UDF_INT_RAW", + 16, + 1, + "r" + ], + [ + "HOST_SLC1_TX_OVF_INT_RAW", + 17, + 1, + "r" + ], + [ + "HOST_SLC1_RX_PF_VALID_INT_RAW", + 18, + 1, + "r" + ], + [ + "HOST_SLC1_EXT_BIT0_INT_RAW", + 19, + 1, + "r" + ], + [ + "HOST_SLC1_EXT_BIT1_INT_RAW", + 20, + 1, + "r" + ], + [ + "HOST_SLC1_EXT_BIT2_INT_RAW", + 21, + 1, + "r" + ], + [ + "HOST_SLC1_EXT_BIT3_INT_RAW", + 22, + 1, + "r" + ], + [ + "HOST_SLC1_WIFI_RX_NEW_PACKET_INT_RAW", + 23, + 1, + "r" + ], + [ + "HOST_SLC1_HOST_RD_RETRY_INT_RAW", + 24, + 1, + "r" + ], + [ + "HOST_SLC1_BT_RX_NEW_PACKET_INT_RAW", + 25, + 1, + "r" + ] + ] + }, + { + "name": "HOST_SLC0HOST_INT_ST", + "offset": "0x58", + "fields": [ + [ + "HOST_SLC0_TOHOST_BIT0_INT_ST", + 0, + 1, + "r" + ], + [ + "HOST_SLC0_TOHOST_BIT1_INT_ST", + 1, + 1, + "r" + ], + [ + "HOST_SLC0_TOHOST_BIT2_INT_ST", + 2, + 1, + "r" + ], + [ + "HOST_SLC0_TOHOST_BIT3_INT_ST", + 3, + 1, + "r" + ], + [ + "HOST_SLC0_TOHOST_BIT4_INT_ST", + 4, + 1, + "r" + ], + [ + "HOST_SLC0_TOHOST_BIT5_INT_ST", + 5, + 1, + "r" + ], + [ + "HOST_SLC0_TOHOST_BIT6_INT_ST", + 6, + 1, + "r" + ], + [ + "HOST_SLC0_TOHOST_BIT7_INT_ST", + 7, + 1, + "r" + ], + [ + "HOST_SLC0_TOKEN0_1TO0_INT_ST", + 8, + 1, + "r" + ], + [ + "HOST_SLC0_TOKEN1_1TO0_INT_ST", + 9, + 1, + "r" + ], + [ + "HOST_SLC0_TOKEN0_0TO1_INT_ST", + 10, + 1, + "r" + ], + [ + "HOST_SLC0_TOKEN1_0TO1_INT_ST", + 11, + 1, + "r" + ], + [ + "HOST_SLC0HOST_RX_SOF_INT_ST", + 12, + 1, + "r" + ], + [ + "HOST_SLC0HOST_RX_EOF_INT_ST", + 13, + 1, + "r" + ], + [ + "HOST_SLC0HOST_RX_START_INT_ST", + 14, + 1, + "r" + ], + [ + "HOST_SLC0HOST_TX_START_INT_ST", + 15, + 1, + "r" + ], + [ + "HOST_SLC0_RX_UDF_INT_ST", + 16, + 1, + "r" + ], + [ + "HOST_SLC0_TX_OVF_INT_ST", + 17, + 1, + "r" + ], + [ + "HOST_SLC0_RX_PF_VALID_INT_ST", + 18, + 1, + "r" + ], + [ + "HOST_SLC0_EXT_BIT0_INT_ST", + 19, + 1, + "r" + ], + [ + "HOST_SLC0_EXT_BIT1_INT_ST", + 20, + 1, + "r" + ], + [ + "HOST_SLC0_EXT_BIT2_INT_ST", + 21, + 1, + "r" + ], + [ + "HOST_SLC0_EXT_BIT3_INT_ST", + 22, + 1, + "r" + ], + [ + "HOST_SLC0_RX_NEW_PACKET_INT_ST", + 23, + 1, + "r" + ], + [ + "HOST_SLC0_HOST_RD_RETRY_INT_ST", + 24, + 1, + "r" + ], + [ + "HOST_GPIO_SDIO_INT_ST", + 25, + 1, + "r" + ] + ] + }, + { + "name": "HOST_SLC1HOST_INT_ST", + "offset": "0x5C", + "fields": [ + [ + "HOST_SLC1_TOHOST_BIT0_INT_ST", + 0, + 1, + "r" + ], + [ + "HOST_SLC1_TOHOST_BIT1_INT_ST", + 1, + 1, + "r" + ], + [ + "HOST_SLC1_TOHOST_BIT2_INT_ST", + 2, + 1, + "r" + ], + [ + "HOST_SLC1_TOHOST_BIT3_INT_ST", + 3, + 1, + "r" + ], + [ + "HOST_SLC1_TOHOST_BIT4_INT_ST", + 4, + 1, + "r" + ], + [ + "HOST_SLC1_TOHOST_BIT5_INT_ST", + 5, + 1, + "r" + ], + [ + "HOST_SLC1_TOHOST_BIT6_INT_ST", + 6, + 1, + "r" + ], + [ + "HOST_SLC1_TOHOST_BIT7_INT_ST", + 7, + 1, + "r" + ], + [ + "HOST_SLC1_TOKEN0_1TO0_INT_ST", + 8, + 1, + "r" + ], + [ + "HOST_SLC1_TOKEN1_1TO0_INT_ST", + 9, + 1, + "r" + ], + [ + "HOST_SLC1_TOKEN0_0TO1_INT_ST", + 10, + 1, + "r" + ], + [ + "HOST_SLC1_TOKEN1_0TO1_INT_ST", + 11, + 1, + "r" + ], + [ + "HOST_SLC1HOST_RX_SOF_INT_ST", + 12, + 1, + "r" + ], + [ + "HOST_SLC1HOST_RX_EOF_INT_ST", + 13, + 1, + "r" + ], + [ + "HOST_SLC1HOST_RX_START_INT_ST", + 14, + 1, + "r" + ], + [ + "HOST_SLC1HOST_TX_START_INT_ST", + 15, + 1, + "r" + ], + [ + "HOST_SLC1_RX_UDF_INT_ST", + 16, + 1, + "r" + ], + [ + "HOST_SLC1_TX_OVF_INT_ST", + 17, + 1, + "r" + ], + [ + "HOST_SLC1_RX_PF_VALID_INT_ST", + 18, + 1, + "r" + ], + [ + "HOST_SLC1_EXT_BIT0_INT_ST", + 19, + 1, + "r" + ], + [ + "HOST_SLC1_EXT_BIT1_INT_ST", + 20, + 1, + "r" + ], + [ + "HOST_SLC1_EXT_BIT2_INT_ST", + 21, + 1, + "r" + ], + [ + "HOST_SLC1_EXT_BIT3_INT_ST", + 22, + 1, + "r" + ], + [ + "HOST_SLC1_WIFI_RX_NEW_PACKET_INT_ST", + 23, + 1, + "r" + ], + [ + "HOST_SLC1_HOST_RD_RETRY_INT_ST", + 24, + 1, + "r" + ], + [ + "HOST_SLC1_BT_RX_NEW_PACKET_INT_ST", + 25, + 1, + "r" + ] + ] + }, + { + "name": "HOST_SLCHOST_PKT_LEN", + "offset": "0x60", + "fields": [ + [ + "HOST_HOSTSLC0_LEN", + 0, + 20, + "r" + ], + [ + "HOST_HOSTSLC0_LEN_CHECK", + 20, + 12, + "r" + ] + ] + }, + { + "name": "HOST_SLCHOST_STATE_W0", + "offset": "0x64", + "fields": [ + [ + "HOST_SLCHOST_STATE0", + 0, + 8, + "r" + ], + [ + "HOST_SLCHOST_STATE1", + 8, + 8, + "r" + ], + [ + "HOST_SLCHOST_STATE2", + 16, + 8, + "r" + ], + [ + "HOST_SLCHOST_STATE3", + 24, + 8, + "r" + ] + ] + }, + { + "name": "HOST_SLCHOST_STATE_W1", + "offset": "0x68", + "fields": [ + [ + "HOST_SLCHOST_STATE4", + 0, + 8, + "r" + ], + [ + "HOST_SLCHOST_STATE5", + 8, + 8, + "r" + ], + [ + "HOST_SLCHOST_STATE6", + 16, + 8, + "r" + ], + [ + "HOST_SLCHOST_STATE7", + 24, + 8, + "r" + ] + ] + }, + { + "name": "HOST_SLCHOST_CONF_W0", + "offset": "0x6C", + "fields": [ + [ + "HOST_SLCHOST_CONF0", + 0, + 8 + ], + [ + "HOST_SLCHOST_CONF1", + 8, + 8 + ], + [ + "HOST_SLCHOST_CONF2", + 16, + 8 + ], + [ + "HOST_SLCHOST_CONF3", + 24, + 8 + ] + ] + }, + { + "name": "HOST_SLCHOST_CONF_W1", + "offset": "0x70", + "fields": [ + [ + "HOST_SLCHOST_CONF4", + 0, + 8 + ], + [ + "HOST_SLCHOST_CONF5", + 8, + 8 + ], + [ + "HOST_SLCHOST_CONF6", + 16, + 8 + ], + [ + "HOST_SLCHOST_CONF7", + 24, + 8 + ] + ] + }, + { + "name": "HOST_SLCHOST_CONF_W2", + "offset": "0x74", + "fields": [ + [ + "HOST_SLCHOST_CONF8", + 0, + 8 + ], + [ + "HOST_SLCHOST_CONF9", + 8, + 8 + ], + [ + "HOST_SLCHOST_CONF10", + 16, + 8 + ], + [ + "HOST_SLCHOST_CONF11", + 24, + 8 + ] + ] + }, + { + "name": "HOST_SLCHOST_CONF_W3", + "offset": "0x78", + "reset": "0x000000C0", + "fields": [ + [ + "HOST_SLCHOST_CONF12", + 0, + 8 + ], + [ + "HOST_SLCHOST_CONF13", + 8, + 8 + ], + [ + "HOST_SLCHOST_CONF14", + 16, + 8 + ], + [ + "HOST_SLCHOST_CONF15", + 24, + 8 + ] + ] + }, + { + "name": "HOST_SLCHOST_CONF_W4", + "offset": "0x7C", + "reset": "0x000001FF", + "fields": [ + [ + "HOST_SLCHOST_CONF16", + 0, + 8 + ], + [ + "HOST_SLCHOST_CONF17", + 8, + 8 + ], + [ + "HOST_SLCHOST_CONF18", + 16, + 8 + ], + [ + "HOST_SLCHOST_CONF19", + 24, + 8 + ] + ] + }, + { + "name": "HOST_SLCHOST_CONF_W5", + "offset": "0x80", + "fields": [ + [ + "HOST_SLCHOST_CONF20", + 0, + 8 + ], + [ + "HOST_SLCHOST_CONF21", + 8, + 8 + ], + [ + "HOST_SLCHOST_CONF22", + 16, + 8 + ], + [ + "HOST_SLCHOST_CONF23", + 24, + 8 + ] + ] + }, + { + "name": "HOST_SLCHOST_WIN_CMD", + "offset": "0x84" + }, + { + "name": "HOST_SLCHOST_CONF_W6", + "offset": "0x88", + "fields": [ + [ + "HOST_SLCHOST_CONF24", + 0, + 8 + ], + [ + "HOST_SLCHOST_CONF25", + 8, + 8 + ], + [ + "HOST_SLCHOST_CONF26", + 16, + 8 + ], + [ + "HOST_SLCHOST_CONF27", + 24, + 8 + ] + ] + }, + { + "name": "HOST_SLCHOST_CONF_W7", + "offset": "0x8C", + "fields": [ + [ + "HOST_SLCHOST_CONF28", + 0, + 8 + ], + [ + "HOST_SLCHOST_CONF29", + 8, + 8 + ], + [ + "HOST_SLCHOST_CONF30", + 16, + 8 + ], + [ + "HOST_SLCHOST_CONF31", + 24, + 8 + ] + ] + }, + { + "name": "HOST_SLCHOST_PKT_LEN0", + "offset": "0x90", + "fields": [ + [ + "HOST_HOSTSLC0_LEN0", + 0, + 20, + "r" + ] + ] + }, + { + "name": "HOST_SLCHOST_PKT_LEN1", + "offset": "0x94", + "fields": [ + [ + "HOST_HOSTSLC0_LEN1", + 0, + 20, + "r" + ] + ] + }, + { + "name": "HOST_SLCHOST_PKT_LEN2", + "offset": "0x98", + "fields": [ + [ + "HOST_HOSTSLC0_LEN2", + 0, + 20, + "r" + ] + ] + }, + { + "name": "HOST_SLCHOST_CONF_W8", + "offset": "0x9C", + "fields": [ + [ + "HOST_SLCHOST_CONF32", + 0, + 8 + ], + [ + "HOST_SLCHOST_CONF33", + 8, + 8 + ], + [ + "HOST_SLCHOST_CONF34", + 16, + 8 + ], + [ + "HOST_SLCHOST_CONF35", + 24, + 8 + ] + ] + }, + { + "name": "HOST_SLCHOST_CONF_W9", + "offset": "0xA0", + "fields": [ + [ + "HOST_SLCHOST_CONF36", + 0, + 8 + ], + [ + "HOST_SLCHOST_CONF37", + 8, + 8 + ], + [ + "HOST_SLCHOST_CONF38", + 16, + 8 + ], + [ + "HOST_SLCHOST_CONF39", + 24, + 8 + ] + ] + }, + { + "name": "HOST_SLCHOST_CONF_W10", + "offset": "0xA4", + "fields": [ + [ + "HOST_SLCHOST_CONF40", + 0, + 8 + ], + [ + "HOST_SLCHOST_CONF41", + 8, + 8 + ], + [ + "HOST_SLCHOST_CONF42", + 16, + 8 + ], + [ + "HOST_SLCHOST_CONF43", + 24, + 8 + ] + ] + }, + { + "name": "HOST_SLCHOST_CONF_W11", + "offset": "0xA8", + "fields": [ + [ + "HOST_SLCHOST_CONF44", + 0, + 8 + ], + [ + "HOST_SLCHOST_CONF45", + 8, + 8 + ], + [ + "HOST_SLCHOST_CONF46", + 16, + 8 + ], + [ + "HOST_SLCHOST_CONF47", + 24, + 8 + ] + ] + }, + { + "name": "HOST_SLCHOST_CONF_W12", + "offset": "0xAC", + "fields": [ + [ + "HOST_SLCHOST_CONF48", + 0, + 8 + ], + [ + "HOST_SLCHOST_CONF49", + 8, + 8 + ], + [ + "HOST_SLCHOST_CONF50", + 16, + 8 + ], + [ + "HOST_SLCHOST_CONF51", + 24, + 8 + ] + ] + }, + { + "name": "HOST_SLCHOST_CONF_W13", + "offset": "0xB0", + "fields": [ + [ + "HOST_SLCHOST_CONF52", + 0, + 8 + ], + [ + "HOST_SLCHOST_CONF53", + 8, + 8 + ], + [ + "HOST_SLCHOST_CONF54", + 16, + 8 + ], + [ + "HOST_SLCHOST_CONF55", + 24, + 8 + ] + ] + }, + { + "name": "HOST_SLCHOST_CONF_W14", + "offset": "0xB4", + "fields": [ + [ + "HOST_SLCHOST_CONF56", + 0, + 8 + ], + [ + "HOST_SLCHOST_CONF57", + 8, + 8 + ], + [ + "HOST_SLCHOST_CONF58", + 16, + 8 + ], + [ + "HOST_SLCHOST_CONF59", + 24, + 8 + ] + ] + }, + { + "name": "HOST_SLCHOST_CONF_W15", + "offset": "0xB8", + "fields": [ + [ + "HOST_SLCHOST_CONF60", + 0, + 8 + ], + [ + "HOST_SLCHOST_CONF61", + 8, + 8 + ], + [ + "HOST_SLCHOST_CONF62", + 16, + 8 + ], + [ + "HOST_SLCHOST_CONF63", + 24, + 8 + ] + ] + }, + { + "name": "HOST_SLCHOST_CHECK_SUM0", + "offset": "0xBC", + "fields": [ + [ + "HOST_SLCHOST_CHECK_SUM0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "HOST_SLCHOST_CHECK_SUM1", + "offset": "0xC0", + "fields": [ + [ + "HOST_SLCHOST_CHECK_SUM1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "HOST_SLC1HOST_TOKEN_RDATA", + "offset": "0xC4", + "fields": [ + [ + "HOST_SLC1_TOKEN0", + 0, + 12, + "r" + ], + [ + "HOST_SLC1_RX_PF_VALID", + 12, + 1, + "r" + ], + [ + "HOST_HOSTSLC1_TOKEN1", + 16, + 12, + "r" + ], + [ + "HOST_SLC1_RX_PF_EOF", + 28, + 4, + "r" + ] + ] + }, + { + "name": "HOST_SLC0HOST_TOKEN_WDATA", + "offset": "0xC8", + "fields": [ + [ + "HOST_SLC0HOST_TOKEN0_WD", + 0, + 12 + ], + [ + "HOST_SLC0HOST_TOKEN1_WD", + 16, + 12 + ] + ] + }, + { + "name": "HOST_SLC1HOST_TOKEN_WDATA", + "offset": "0xCC", + "fields": [ + [ + "HOST_SLC1HOST_TOKEN0_WD", + 0, + 12 + ], + [ + "HOST_SLC1HOST_TOKEN1_WD", + 16, + 12 + ] + ] + }, + { + "name": "HOST_SLCHOST_TOKEN_CON", + "offset": "0xD0", + "fields": [ + [ + "HOST_SLC0HOST_TOKEN0_DEC", + 0, + 1, + "w" + ], + [ + "HOST_SLC0HOST_TOKEN1_DEC", + 1, + 1, + "w" + ], + [ + "HOST_SLC0HOST_TOKEN0_WR", + 2, + 1, + "w" + ], + [ + "HOST_SLC0HOST_TOKEN1_WR", + 3, + 1, + "w" + ], + [ + "HOST_SLC1HOST_TOKEN0_DEC", + 4, + 1, + "w" + ], + [ + "HOST_SLC1HOST_TOKEN1_DEC", + 5, + 1, + "w" + ], + [ + "HOST_SLC1HOST_TOKEN0_WR", + 6, + 1, + "w" + ], + [ + "HOST_SLC1HOST_TOKEN1_WR", + 7, + 1, + "w" + ], + [ + "HOST_SLC0HOST_LEN_WR", + 8, + 1, + "w" + ] + ] + }, + { + "name": "HOST_SLC0HOST_INT_CLR", + "offset": "0xD4", + "fields": [ + [ + "HOST_SLC0_TOHOST_BIT0_INT_CLR", + 0, + 1, + "w" + ], + [ + "HOST_SLC0_TOHOST_BIT1_INT_CLR", + 1, + 1, + "w" + ], + [ + "HOST_SLC0_TOHOST_BIT2_INT_CLR", + 2, + 1, + "w" + ], + [ + "HOST_SLC0_TOHOST_BIT3_INT_CLR", + 3, + 1, + "w" + ], + [ + "HOST_SLC0_TOHOST_BIT4_INT_CLR", + 4, + 1, + "w" + ], + [ + "HOST_SLC0_TOHOST_BIT5_INT_CLR", + 5, + 1, + "w" + ], + [ + "HOST_SLC0_TOHOST_BIT6_INT_CLR", + 6, + 1, + "w" + ], + [ + "HOST_SLC0_TOHOST_BIT7_INT_CLR", + 7, + 1, + "w" + ], + [ + "HOST_SLC0_TOKEN0_1TO0_INT_CLR", + 8, + 1, + "w" + ], + [ + "HOST_SLC0_TOKEN1_1TO0_INT_CLR", + 9, + 1, + "w" + ], + [ + "HOST_SLC0_TOKEN0_0TO1_INT_CLR", + 10, + 1, + "w" + ], + [ + "HOST_SLC0_TOKEN1_0TO1_INT_CLR", + 11, + 1, + "w" + ], + [ + "HOST_SLC0HOST_RX_SOF_INT_CLR", + 12, + 1, + "w" + ], + [ + "HOST_SLC0HOST_RX_EOF_INT_CLR", + 13, + 1, + "w" + ], + [ + "HOST_SLC0HOST_RX_START_INT_CLR", + 14, + 1, + "w" + ], + [ + "HOST_SLC0HOST_TX_START_INT_CLR", + 15, + 1, + "w" + ], + [ + "HOST_SLC0_RX_UDF_INT_CLR", + 16, + 1, + "w" + ], + [ + "HOST_SLC0_TX_OVF_INT_CLR", + 17, + 1, + "w" + ], + [ + "HOST_SLC0_RX_PF_VALID_INT_CLR", + 18, + 1, + "w" + ], + [ + "HOST_SLC0_EXT_BIT0_INT_CLR", + 19, + 1, + "w" + ], + [ + "HOST_SLC0_EXT_BIT1_INT_CLR", + 20, + 1, + "w" + ], + [ + "HOST_SLC0_EXT_BIT2_INT_CLR", + 21, + 1, + "w" + ], + [ + "HOST_SLC0_EXT_BIT3_INT_CLR", + 22, + 1, + "w" + ], + [ + "HOST_SLC0_RX_NEW_PACKET_INT_CLR", + 23, + 1, + "w" + ], + [ + "HOST_SLC0_HOST_RD_RETRY_INT_CLR", + 24, + 1, + "w" + ], + [ + "HOST_GPIO_SDIO_INT_CLR", + 25, + 1, + "w" + ] + ] + }, + { + "name": "HOST_SLC1HOST_INT_CLR", + "offset": "0xD8", + "fields": [ + [ + "HOST_SLC1_TOHOST_BIT0_INT_CLR", + 0, + 1, + "w" + ], + [ + "HOST_SLC1_TOHOST_BIT1_INT_CLR", + 1, + 1, + "w" + ], + [ + "HOST_SLC1_TOHOST_BIT2_INT_CLR", + 2, + 1, + "w" + ], + [ + "HOST_SLC1_TOHOST_BIT3_INT_CLR", + 3, + 1, + "w" + ], + [ + "HOST_SLC1_TOHOST_BIT4_INT_CLR", + 4, + 1, + "w" + ], + [ + "HOST_SLC1_TOHOST_BIT5_INT_CLR", + 5, + 1, + "w" + ], + [ + "HOST_SLC1_TOHOST_BIT6_INT_CLR", + 6, + 1, + "w" + ], + [ + "HOST_SLC1_TOHOST_BIT7_INT_CLR", + 7, + 1, + "w" + ], + [ + "HOST_SLC1_TOKEN0_1TO0_INT_CLR", + 8, + 1, + "w" + ], + [ + "HOST_SLC1_TOKEN1_1TO0_INT_CLR", + 9, + 1, + "w" + ], + [ + "HOST_SLC1_TOKEN0_0TO1_INT_CLR", + 10, + 1, + "w" + ], + [ + "HOST_SLC1_TOKEN1_0TO1_INT_CLR", + 11, + 1, + "w" + ], + [ + "HOST_SLC1HOST_RX_SOF_INT_CLR", + 12, + 1, + "w" + ], + [ + "HOST_SLC1HOST_RX_EOF_INT_CLR", + 13, + 1, + "w" + ], + [ + "HOST_SLC1HOST_RX_START_INT_CLR", + 14, + 1, + "w" + ], + [ + "HOST_SLC1HOST_TX_START_INT_CLR", + 15, + 1, + "w" + ], + [ + "HOST_SLC1_RX_UDF_INT_CLR", + 16, + 1, + "w" + ], + [ + "HOST_SLC1_TX_OVF_INT_CLR", + 17, + 1, + "w" + ], + [ + "HOST_SLC1_RX_PF_VALID_INT_CLR", + 18, + 1, + "w" + ], + [ + "HOST_SLC1_EXT_BIT0_INT_CLR", + 19, + 1, + "w" + ], + [ + "HOST_SLC1_EXT_BIT1_INT_CLR", + 20, + 1, + "w" + ], + [ + "HOST_SLC1_EXT_BIT2_INT_CLR", + 21, + 1, + "w" + ], + [ + "HOST_SLC1_EXT_BIT3_INT_CLR", + 22, + 1, + "w" + ], + [ + "HOST_SLC1_WIFI_RX_NEW_PACKET_INT_CLR", + 23, + 1, + "w" + ], + [ + "HOST_SLC1_HOST_RD_RETRY_INT_CLR", + 24, + 1, + "w" + ], + [ + "HOST_SLC1_BT_RX_NEW_PACKET_INT_CLR", + 25, + 1, + "w" + ] + ] + }, + { + "name": "HOST_SLC0HOST_FUNC1_INT_ENA", + "offset": "0xDC", + "fields": [ + [ + "HOST_FN1_SLC0_TOHOST_BIT0_INT_ENA", + 0, + 1 + ], + [ + "HOST_FN1_SLC0_TOHOST_BIT1_INT_ENA", + 1, + 1 + ], + [ + "HOST_FN1_SLC0_TOHOST_BIT2_INT_ENA", + 2, + 1 + ], + [ + "HOST_FN1_SLC0_TOHOST_BIT3_INT_ENA", + 3, + 1 + ], + [ + "HOST_FN1_SLC0_TOHOST_BIT4_INT_ENA", + 4, + 1 + ], + [ + "HOST_FN1_SLC0_TOHOST_BIT5_INT_ENA", + 5, + 1 + ], + [ + "HOST_FN1_SLC0_TOHOST_BIT6_INT_ENA", + 6, + 1 + ], + [ + "HOST_FN1_SLC0_TOHOST_BIT7_INT_ENA", + 7, + 1 + ], + [ + "HOST_FN1_SLC0_TOKEN0_1TO0_INT_ENA", + 8, + 1 + ], + [ + "HOST_FN1_SLC0_TOKEN1_1TO0_INT_ENA", + 9, + 1 + ], + [ + "HOST_FN1_SLC0_TOKEN0_0TO1_INT_ENA", + 10, + 1 + ], + [ + "HOST_FN1_SLC0_TOKEN1_0TO1_INT_ENA", + 11, + 1 + ], + [ + "HOST_FN1_SLC0HOST_RX_SOF_INT_ENA", + 12, + 1 + ], + [ + "HOST_FN1_SLC0HOST_RX_EOF_INT_ENA", + 13, + 1 + ], + [ + "HOST_FN1_SLC0HOST_RX_START_INT_ENA", + 14, + 1 + ], + [ + "HOST_FN1_SLC0HOST_TX_START_INT_ENA", + 15, + 1 + ], + [ + "HOST_FN1_SLC0_RX_UDF_INT_ENA", + 16, + 1 + ], + [ + "HOST_FN1_SLC0_TX_OVF_INT_ENA", + 17, + 1 + ], + [ + "HOST_FN1_SLC0_RX_PF_VALID_INT_ENA", + 18, + 1 + ], + [ + "HOST_FN1_SLC0_EXT_BIT0_INT_ENA", + 19, + 1 + ], + [ + "HOST_FN1_SLC0_EXT_BIT1_INT_ENA", + 20, + 1 + ], + [ + "HOST_FN1_SLC0_EXT_BIT2_INT_ENA", + 21, + 1 + ], + [ + "HOST_FN1_SLC0_EXT_BIT3_INT_ENA", + 22, + 1 + ], + [ + "HOST_FN1_SLC0_RX_NEW_PACKET_INT_ENA", + 23, + 1 + ], + [ + "HOST_FN1_SLC0_HOST_RD_RETRY_INT_ENA", + 24, + 1 + ], + [ + "HOST_FN1_GPIO_SDIO_INT_ENA", + 25, + 1 + ] + ] + }, + { + "name": "HOST_SLC1HOST_FUNC1_INT_ENA", + "offset": "0xE0", + "fields": [ + [ + "HOST_FN1_SLC1_TOHOST_BIT0_INT_ENA", + 0, + 1 + ], + [ + "HOST_FN1_SLC1_TOHOST_BIT1_INT_ENA", + 1, + 1 + ], + [ + "HOST_FN1_SLC1_TOHOST_BIT2_INT_ENA", + 2, + 1 + ], + [ + "HOST_FN1_SLC1_TOHOST_BIT3_INT_ENA", + 3, + 1 + ], + [ + "HOST_FN1_SLC1_TOHOST_BIT4_INT_ENA", + 4, + 1 + ], + [ + "HOST_FN1_SLC1_TOHOST_BIT5_INT_ENA", + 5, + 1 + ], + [ + "HOST_FN1_SLC1_TOHOST_BIT6_INT_ENA", + 6, + 1 + ], + [ + "HOST_FN1_SLC1_TOHOST_BIT7_INT_ENA", + 7, + 1 + ], + [ + "HOST_FN1_SLC1_TOKEN0_1TO0_INT_ENA", + 8, + 1 + ], + [ + "HOST_FN1_SLC1_TOKEN1_1TO0_INT_ENA", + 9, + 1 + ], + [ + "HOST_FN1_SLC1_TOKEN0_0TO1_INT_ENA", + 10, + 1 + ], + [ + "HOST_FN1_SLC1_TOKEN1_0TO1_INT_ENA", + 11, + 1 + ], + [ + "HOST_FN1_SLC1HOST_RX_SOF_INT_ENA", + 12, + 1 + ], + [ + "HOST_FN1_SLC1HOST_RX_EOF_INT_ENA", + 13, + 1 + ], + [ + "HOST_FN1_SLC1HOST_RX_START_INT_ENA", + 14, + 1 + ], + [ + "HOST_FN1_SLC1HOST_TX_START_INT_ENA", + 15, + 1 + ], + [ + "HOST_FN1_SLC1_RX_UDF_INT_ENA", + 16, + 1 + ], + [ + "HOST_FN1_SLC1_TX_OVF_INT_ENA", + 17, + 1 + ], + [ + "HOST_FN1_SLC1_RX_PF_VALID_INT_ENA", + 18, + 1 + ], + [ + "HOST_FN1_SLC1_EXT_BIT0_INT_ENA", + 19, + 1 + ], + [ + "HOST_FN1_SLC1_EXT_BIT1_INT_ENA", + 20, + 1 + ], + [ + "HOST_FN1_SLC1_EXT_BIT2_INT_ENA", + 21, + 1 + ], + [ + "HOST_FN1_SLC1_EXT_BIT3_INT_ENA", + 22, + 1 + ], + [ + "HOST_FN1_SLC1_WIFI_RX_NEW_PACKET_INT_ENA", + 23, + 1 + ], + [ + "HOST_FN1_SLC1_HOST_RD_RETRY_INT_ENA", + 24, + 1 + ], + [ + "HOST_FN1_SLC1_BT_RX_NEW_PACKET_INT_ENA", + 25, + 1 + ] + ] + }, + { + "name": "HOST_SLC0HOST_FUNC2_INT_ENA", + "offset": "0xE4", + "fields": [ + [ + "HOST_FN2_SLC0_TOHOST_BIT0_INT_ENA", + 0, + 1 + ], + [ + "HOST_FN2_SLC0_TOHOST_BIT1_INT_ENA", + 1, + 1 + ], + [ + "HOST_FN2_SLC0_TOHOST_BIT2_INT_ENA", + 2, + 1 + ], + [ + "HOST_FN2_SLC0_TOHOST_BIT3_INT_ENA", + 3, + 1 + ], + [ + "HOST_FN2_SLC0_TOHOST_BIT4_INT_ENA", + 4, + 1 + ], + [ + "HOST_FN2_SLC0_TOHOST_BIT5_INT_ENA", + 5, + 1 + ], + [ + "HOST_FN2_SLC0_TOHOST_BIT6_INT_ENA", + 6, + 1 + ], + [ + "HOST_FN2_SLC0_TOHOST_BIT7_INT_ENA", + 7, + 1 + ], + [ + "HOST_FN2_SLC0_TOKEN0_1TO0_INT_ENA", + 8, + 1 + ], + [ + "HOST_FN2_SLC0_TOKEN1_1TO0_INT_ENA", + 9, + 1 + ], + [ + "HOST_FN2_SLC0_TOKEN0_0TO1_INT_ENA", + 10, + 1 + ], + [ + "HOST_FN2_SLC0_TOKEN1_0TO1_INT_ENA", + 11, + 1 + ], + [ + "HOST_FN2_SLC0HOST_RX_SOF_INT_ENA", + 12, + 1 + ], + [ + "HOST_FN2_SLC0HOST_RX_EOF_INT_ENA", + 13, + 1 + ], + [ + "HOST_FN2_SLC0HOST_RX_START_INT_ENA", + 14, + 1 + ], + [ + "HOST_FN2_SLC0HOST_TX_START_INT_ENA", + 15, + 1 + ], + [ + "HOST_FN2_SLC0_RX_UDF_INT_ENA", + 16, + 1 + ], + [ + "HOST_FN2_SLC0_TX_OVF_INT_ENA", + 17, + 1 + ], + [ + "HOST_FN2_SLC0_RX_PF_VALID_INT_ENA", + 18, + 1 + ], + [ + "HOST_FN2_SLC0_EXT_BIT0_INT_ENA", + 19, + 1 + ], + [ + "HOST_FN2_SLC0_EXT_BIT1_INT_ENA", + 20, + 1 + ], + [ + "HOST_FN2_SLC0_EXT_BIT2_INT_ENA", + 21, + 1 + ], + [ + "HOST_FN2_SLC0_EXT_BIT3_INT_ENA", + 22, + 1 + ], + [ + "HOST_FN2_SLC0_RX_NEW_PACKET_INT_ENA", + 23, + 1 + ], + [ + "HOST_FN2_SLC0_HOST_RD_RETRY_INT_ENA", + 24, + 1 + ], + [ + "HOST_FN2_GPIO_SDIO_INT_ENA", + 25, + 1 + ] + ] + }, + { + "name": "HOST_SLC1HOST_FUNC2_INT_ENA", + "offset": "0xE8", + "fields": [ + [ + "HOST_FN2_SLC1_TOHOST_BIT0_INT_ENA", + 0, + 1 + ], + [ + "HOST_FN2_SLC1_TOHOST_BIT1_INT_ENA", + 1, + 1 + ], + [ + "HOST_FN2_SLC1_TOHOST_BIT2_INT_ENA", + 2, + 1 + ], + [ + "HOST_FN2_SLC1_TOHOST_BIT3_INT_ENA", + 3, + 1 + ], + [ + "HOST_FN2_SLC1_TOHOST_BIT4_INT_ENA", + 4, + 1 + ], + [ + "HOST_FN2_SLC1_TOHOST_BIT5_INT_ENA", + 5, + 1 + ], + [ + "HOST_FN2_SLC1_TOHOST_BIT6_INT_ENA", + 6, + 1 + ], + [ + "HOST_FN2_SLC1_TOHOST_BIT7_INT_ENA", + 7, + 1 + ], + [ + "HOST_FN2_SLC1_TOKEN0_1TO0_INT_ENA", + 8, + 1 + ], + [ + "HOST_FN2_SLC1_TOKEN1_1TO0_INT_ENA", + 9, + 1 + ], + [ + "HOST_FN2_SLC1_TOKEN0_0TO1_INT_ENA", + 10, + 1 + ], + [ + "HOST_FN2_SLC1_TOKEN1_0TO1_INT_ENA", + 11, + 1 + ], + [ + "HOST_FN2_SLC1HOST_RX_SOF_INT_ENA", + 12, + 1 + ], + [ + "HOST_FN2_SLC1HOST_RX_EOF_INT_ENA", + 13, + 1 + ], + [ + "HOST_FN2_SLC1HOST_RX_START_INT_ENA", + 14, + 1 + ], + [ + "HOST_FN2_SLC1HOST_TX_START_INT_ENA", + 15, + 1 + ], + [ + "HOST_FN2_SLC1_RX_UDF_INT_ENA", + 16, + 1 + ], + [ + "HOST_FN2_SLC1_TX_OVF_INT_ENA", + 17, + 1 + ], + [ + "HOST_FN2_SLC1_RX_PF_VALID_INT_ENA", + 18, + 1 + ], + [ + "HOST_FN2_SLC1_EXT_BIT0_INT_ENA", + 19, + 1 + ], + [ + "HOST_FN2_SLC1_EXT_BIT1_INT_ENA", + 20, + 1 + ], + [ + "HOST_FN2_SLC1_EXT_BIT2_INT_ENA", + 21, + 1 + ], + [ + "HOST_FN2_SLC1_EXT_BIT3_INT_ENA", + 22, + 1 + ], + [ + "HOST_FN2_SLC1_WIFI_RX_NEW_PACKET_INT_ENA", + 23, + 1 + ], + [ + "HOST_FN2_SLC1_HOST_RD_RETRY_INT_ENA", + 24, + 1 + ], + [ + "HOST_FN2_SLC1_BT_RX_NEW_PACKET_INT_ENA", + 25, + 1 + ] + ] + }, + { + "name": "HOST_SLC0HOST_INT_ENA", + "offset": "0xEC", + "fields": [ + [ + "HOST_SLC0_TOHOST_BIT0_INT_ENA", + 0, + 1 + ], + [ + "HOST_SLC0_TOHOST_BIT1_INT_ENA", + 1, + 1 + ], + [ + "HOST_SLC0_TOHOST_BIT2_INT_ENA", + 2, + 1 + ], + [ + "HOST_SLC0_TOHOST_BIT3_INT_ENA", + 3, + 1 + ], + [ + "HOST_SLC0_TOHOST_BIT4_INT_ENA", + 4, + 1 + ], + [ + "HOST_SLC0_TOHOST_BIT5_INT_ENA", + 5, + 1 + ], + [ + "HOST_SLC0_TOHOST_BIT6_INT_ENA", + 6, + 1 + ], + [ + "HOST_SLC0_TOHOST_BIT7_INT_ENA", + 7, + 1 + ], + [ + "HOST_SLC0_TOKEN0_1TO0_INT_ENA", + 8, + 1 + ], + [ + "HOST_SLC0_TOKEN1_1TO0_INT_ENA", + 9, + 1 + ], + [ + "HOST_SLC0_TOKEN0_0TO1_INT_ENA", + 10, + 1 + ], + [ + "HOST_SLC0_TOKEN1_0TO1_INT_ENA", + 11, + 1 + ], + [ + "HOST_SLC0HOST_RX_SOF_INT_ENA", + 12, + 1 + ], + [ + "HOST_SLC0HOST_RX_EOF_INT_ENA", + 13, + 1 + ], + [ + "HOST_SLC0HOST_RX_START_INT_ENA", + 14, + 1 + ], + [ + "HOST_SLC0HOST_TX_START_INT_ENA", + 15, + 1 + ], + [ + "HOST_SLC0_RX_UDF_INT_ENA", + 16, + 1 + ], + [ + "HOST_SLC0_TX_OVF_INT_ENA", + 17, + 1 + ], + [ + "HOST_SLC0_RX_PF_VALID_INT_ENA", + 18, + 1 + ], + [ + "HOST_SLC0_EXT_BIT0_INT_ENA", + 19, + 1 + ], + [ + "HOST_SLC0_EXT_BIT1_INT_ENA", + 20, + 1 + ], + [ + "HOST_SLC0_EXT_BIT2_INT_ENA", + 21, + 1 + ], + [ + "HOST_SLC0_EXT_BIT3_INT_ENA", + 22, + 1 + ], + [ + "HOST_SLC0_RX_NEW_PACKET_INT_ENA", + 23, + 1 + ], + [ + "HOST_SLC0_HOST_RD_RETRY_INT_ENA", + 24, + 1 + ], + [ + "HOST_GPIO_SDIO_INT_ENA", + 25, + 1 + ] + ] + }, + { + "name": "HOST_SLC1HOST_INT_ENA", + "offset": "0xF0", + "fields": [ + [ + "HOST_SLC1_TOHOST_BIT0_INT_ENA", + 0, + 1 + ], + [ + "HOST_SLC1_TOHOST_BIT1_INT_ENA", + 1, + 1 + ], + [ + "HOST_SLC1_TOHOST_BIT2_INT_ENA", + 2, + 1 + ], + [ + "HOST_SLC1_TOHOST_BIT3_INT_ENA", + 3, + 1 + ], + [ + "HOST_SLC1_TOHOST_BIT4_INT_ENA", + 4, + 1 + ], + [ + "HOST_SLC1_TOHOST_BIT5_INT_ENA", + 5, + 1 + ], + [ + "HOST_SLC1_TOHOST_BIT6_INT_ENA", + 6, + 1 + ], + [ + "HOST_SLC1_TOHOST_BIT7_INT_ENA", + 7, + 1 + ], + [ + "HOST_SLC1_TOKEN0_1TO0_INT_ENA", + 8, + 1 + ], + [ + "HOST_SLC1_TOKEN1_1TO0_INT_ENA", + 9, + 1 + ], + [ + "HOST_SLC1_TOKEN0_0TO1_INT_ENA", + 10, + 1 + ], + [ + "HOST_SLC1_TOKEN1_0TO1_INT_ENA", + 11, + 1 + ], + [ + "HOST_SLC1HOST_RX_SOF_INT_ENA", + 12, + 1 + ], + [ + "HOST_SLC1HOST_RX_EOF_INT_ENA", + 13, + 1 + ], + [ + "HOST_SLC1HOST_RX_START_INT_ENA", + 14, + 1 + ], + [ + "HOST_SLC1HOST_TX_START_INT_ENA", + 15, + 1 + ], + [ + "HOST_SLC1_RX_UDF_INT_ENA", + 16, + 1 + ], + [ + "HOST_SLC1_TX_OVF_INT_ENA", + 17, + 1 + ], + [ + "HOST_SLC1_RX_PF_VALID_INT_ENA", + 18, + 1 + ], + [ + "HOST_SLC1_EXT_BIT0_INT_ENA", + 19, + 1 + ], + [ + "HOST_SLC1_EXT_BIT1_INT_ENA", + 20, + 1 + ], + [ + "HOST_SLC1_EXT_BIT2_INT_ENA", + 21, + 1 + ], + [ + "HOST_SLC1_EXT_BIT3_INT_ENA", + 22, + 1 + ], + [ + "HOST_SLC1_WIFI_RX_NEW_PACKET_INT_ENA", + 23, + 1 + ], + [ + "HOST_SLC1_HOST_RD_RETRY_INT_ENA", + 24, + 1 + ], + [ + "HOST_SLC1_BT_RX_NEW_PACKET_INT_ENA", + 25, + 1 + ] + ] + }, + { + "name": "HOST_SLC0HOST_RX_INFOR", + "offset": "0xF4", + "fields": [ + [ + "HOST_SLC0HOST_RX_INFOR", + 0, + 20 + ] + ] + }, + { + "name": "HOST_SLC1HOST_RX_INFOR", + "offset": "0xF8", + "fields": [ + [ + "HOST_SLC1HOST_RX_INFOR", + 0, + 20 + ] + ] + }, + { + "name": "HOST_SLC0HOST_LEN_WD", + "offset": "0xFC", + "fields": [ + [ + "HOST_SLC0HOST_LEN_WD", + 0, + 32 + ] + ] + }, + { + "name": "HOST_SLC_APBWIN_WDATA", + "offset": "0x100", + "fields": [ + [ + "HOST_SLC_APBWIN_WDATA", + 0, + 32 + ] + ] + }, + { + "name": "HOST_SLC_APBWIN_CONF", + "offset": "0x104", + "fields": [ + [ + "HOST_SLC_APBWIN_ADDR", + 0, + 28 + ], + [ + "HOST_SLC_APBWIN_WR", + 28, + 1 + ], + [ + "HOST_SLC_APBWIN_START", + 29, + 1 + ] + ] + }, + { + "name": "HOST_SLC_APBWIN_RDATA", + "offset": "0x108", + "fields": [ + [ + "HOST_SLC_APBWIN_RDATA", + 0, + 32, + "r" + ] + ] + }, + { + "name": "HOST_SLCHOST_RDCLR0", + "offset": "0x10C", + "reset": "0x0003C044", + "fields": [ + [ + "HOST_SLCHOST_SLC0_BIT7_CLRADDR", + 0, + 9 + ], + [ + "HOST_SLCHOST_SLC0_BIT6_CLRADDR", + 9, + 9 + ] + ] + }, + { + "name": "HOST_SLCHOST_RDCLR1", + "offset": "0x110", + "reset": "0x0003C1E0", + "fields": [ + [ + "HOST_SLCHOST_SLC1_BIT7_CLRADDR", + 0, + 9 + ], + [ + "HOST_SLCHOST_SLC1_BIT6_CLRADDR", + 9, + 9 + ] + ] + }, + { + "name": "HOST_SLC0HOST_INT_ENA1", + "offset": "0x114", + "fields": [ + [ + "HOST_SLC0_TOHOST_BIT0_INT_ENA1", + 0, + 1 + ], + [ + "HOST_SLC0_TOHOST_BIT1_INT_ENA1", + 1, + 1 + ], + [ + "HOST_SLC0_TOHOST_BIT2_INT_ENA1", + 2, + 1 + ], + [ + "HOST_SLC0_TOHOST_BIT3_INT_ENA1", + 3, + 1 + ], + [ + "HOST_SLC0_TOHOST_BIT4_INT_ENA1", + 4, + 1 + ], + [ + "HOST_SLC0_TOHOST_BIT5_INT_ENA1", + 5, + 1 + ], + [ + "HOST_SLC0_TOHOST_BIT6_INT_ENA1", + 6, + 1 + ], + [ + "HOST_SLC0_TOHOST_BIT7_INT_ENA1", + 7, + 1 + ], + [ + "HOST_SLC0_TOKEN0_1TO0_INT_ENA1", + 8, + 1 + ], + [ + "HOST_SLC0_TOKEN1_1TO0_INT_ENA1", + 9, + 1 + ], + [ + "HOST_SLC0_TOKEN0_0TO1_INT_ENA1", + 10, + 1 + ], + [ + "HOST_SLC0_TOKEN1_0TO1_INT_ENA1", + 11, + 1 + ], + [ + "HOST_SLC0HOST_RX_SOF_INT_ENA1", + 12, + 1 + ], + [ + "HOST_SLC0HOST_RX_EOF_INT_ENA1", + 13, + 1 + ], + [ + "HOST_SLC0HOST_RX_START_INT_ENA1", + 14, + 1 + ], + [ + "HOST_SLC0HOST_TX_START_INT_ENA1", + 15, + 1 + ], + [ + "HOST_SLC0_RX_UDF_INT_ENA1", + 16, + 1 + ], + [ + "HOST_SLC0_TX_OVF_INT_ENA1", + 17, + 1 + ], + [ + "HOST_SLC0_RX_PF_VALID_INT_ENA1", + 18, + 1 + ], + [ + "HOST_SLC0_EXT_BIT0_INT_ENA1", + 19, + 1 + ], + [ + "HOST_SLC0_EXT_BIT1_INT_ENA1", + 20, + 1 + ], + [ + "HOST_SLC0_EXT_BIT2_INT_ENA1", + 21, + 1 + ], + [ + "HOST_SLC0_EXT_BIT3_INT_ENA1", + 22, + 1 + ], + [ + "HOST_SLC0_RX_NEW_PACKET_INT_ENA1", + 23, + 1 + ], + [ + "HOST_SLC0_HOST_RD_RETRY_INT_ENA1", + 24, + 1 + ], + [ + "HOST_GPIO_SDIO_INT_ENA1", + 25, + 1 + ] + ] + }, + { + "name": "HOST_SLC1HOST_INT_ENA1", + "offset": "0x118", + "fields": [ + [ + "HOST_SLC1_TOHOST_BIT0_INT_ENA1", + 0, + 1 + ], + [ + "HOST_SLC1_TOHOST_BIT1_INT_ENA1", + 1, + 1 + ], + [ + "HOST_SLC1_TOHOST_BIT2_INT_ENA1", + 2, + 1 + ], + [ + "HOST_SLC1_TOHOST_BIT3_INT_ENA1", + 3, + 1 + ], + [ + "HOST_SLC1_TOHOST_BIT4_INT_ENA1", + 4, + 1 + ], + [ + "HOST_SLC1_TOHOST_BIT5_INT_ENA1", + 5, + 1 + ], + [ + "HOST_SLC1_TOHOST_BIT6_INT_ENA1", + 6, + 1 + ], + [ + "HOST_SLC1_TOHOST_BIT7_INT_ENA1", + 7, + 1 + ], + [ + "HOST_SLC1_TOKEN0_1TO0_INT_ENA1", + 8, + 1 + ], + [ + "HOST_SLC1_TOKEN1_1TO0_INT_ENA1", + 9, + 1 + ], + [ + "HOST_SLC1_TOKEN0_0TO1_INT_ENA1", + 10, + 1 + ], + [ + "HOST_SLC1_TOKEN1_0TO1_INT_ENA1", + 11, + 1 + ], + [ + "HOST_SLC1HOST_RX_SOF_INT_ENA1", + 12, + 1 + ], + [ + "HOST_SLC1HOST_RX_EOF_INT_ENA1", + 13, + 1 + ], + [ + "HOST_SLC1HOST_RX_START_INT_ENA1", + 14, + 1 + ], + [ + "HOST_SLC1HOST_TX_START_INT_ENA1", + 15, + 1 + ], + [ + "HOST_SLC1_RX_UDF_INT_ENA1", + 16, + 1 + ], + [ + "HOST_SLC1_TX_OVF_INT_ENA1", + 17, + 1 + ], + [ + "HOST_SLC1_RX_PF_VALID_INT_ENA1", + 18, + 1 + ], + [ + "HOST_SLC1_EXT_BIT0_INT_ENA1", + 19, + 1 + ], + [ + "HOST_SLC1_EXT_BIT1_INT_ENA1", + 20, + 1 + ], + [ + "HOST_SLC1_EXT_BIT2_INT_ENA1", + 21, + 1 + ], + [ + "HOST_SLC1_EXT_BIT3_INT_ENA1", + 22, + 1 + ], + [ + "HOST_SLC1_WIFI_RX_NEW_PACKET_INT_ENA1", + 23, + 1 + ], + [ + "HOST_SLC1_HOST_RD_RETRY_INT_ENA1", + 24, + 1 + ], + [ + "HOST_SLC1_BT_RX_NEW_PACKET_INT_ENA1", + 25, + 1 + ] + ] + }, + { + "name": "HOST_SLCHOSTDATE", + "offset": "0x178", + "reset": "0x16022500", + "fields": [ + [ + "HOST_SLCHOST_DATE", + 0, + 32 + ] + ] + }, + { + "name": "HOST_SLCHOSTID", + "offset": "0x17C", + "reset": "0x00000600", + "fields": [ + [ + "HOST_SLCHOST_ID", + 0, + 32 + ] + ] + }, + { + "name": "HOST_SLCHOST_CONF", + "offset": "0x1F0", + "fields": [ + [ + "HOST_FRC_SDIO11", + 0, + 5 + ], + [ + "HOST_FRC_SDIO20", + 5, + 5 + ], + [ + "HOST_FRC_NEG_SAMP", + 10, + 5 + ], + [ + "HOST_FRC_POS_SAMP", + 15, + 5 + ], + [ + "HOST_FRC_QUICK_IN", + 20, + 5 + ], + [ + "HOST_SDIO20_INT_DELAY", + 25, + 1 + ], + [ + "HOST_SDIO_PAD_PULLUP", + 26, + 1 + ], + [ + "HOST_HSPEED_CON_EN", + 27, + 1 + ] + ] + }, + { + "name": "HOST_SLCHOST_INF_ST", + "offset": "0x1F4", + "fields": [ + [ + "HOST_SDIO20_MODE", + 0, + 5, + "r" + ], + [ + "HOST_SDIO_NEG_SAMP", + 5, + 5, + "r" + ], + [ + "HOST_SDIO_QUICK_IN", + 10, + 5, + "r" + ] + ] + } + ] + }, + { + "name": "RMT", + "base": "0x3FF56000", + "interrupts": [ + "RMT" + ], + "registers": [ + { + "name": "CH0DATA", + "offset": "0x00" + }, + { + "name": "CH1DATA", + "offset": "0x04" + }, + { + "name": "CH2DATA", + "offset": "0x08" + }, + { + "name": "CH3DATA", + "offset": "0x0C" + }, + { + "name": "CH4DATA", + "offset": "0x10" + }, + { + "name": "CH5DATA", + "offset": "0x14" + }, + { + "name": "CH6DATA", + "offset": "0x18" + }, + { + "name": "CH7DATA", + "offset": "0x1C" + }, + { + "name": "CH0CONF0", + "offset": "0x20", + "reset": "0x31100002", + "fields": [ + [ + "DIV_CNT", + 0, + 8 + ], + [ + "IDLE_THRES", + 8, + 16 + ], + [ + "MEM_SIZE", + 24, + 4 + ], + [ + "CARRIER_EN", + 28, + 1 + ], + [ + "CARRIER_OUT_LV", + 29, + 1 + ], + [ + "MEM_PD", + 30, + 1 + ], + [ + "CLK_EN", + 31, + 1 + ] + ] + }, + { + "name": "CH1CONF0", + "offset": "0x28", + "reset": "0x31100002", + "fields": [ + [ + "DIV_CNT", + 0, + 8 + ], + [ + "IDLE_THRES", + 8, + 16 + ], + [ + "MEM_SIZE", + 24, + 4 + ], + [ + "CARRIER_EN", + 28, + 1 + ], + [ + "CARRIER_OUT_LV", + 29, + 1 + ], + [ + "MEM_PD", + 30, + 1 + ], + [ + "CLK_EN", + 31, + 1 + ] + ] + }, + { + "name": "CH2CONF0", + "offset": "0x30", + "reset": "0x31100002", + "fields": [ + [ + "DIV_CNT", + 0, + 8 + ], + [ + "IDLE_THRES", + 8, + 16 + ], + [ + "MEM_SIZE", + 24, + 4 + ], + [ + "CARRIER_EN", + 28, + 1 + ], + [ + "CARRIER_OUT_LV", + 29, + 1 + ], + [ + "MEM_PD", + 30, + 1 + ], + [ + "CLK_EN", + 31, + 1 + ] + ] + }, + { + "name": "CH3CONF0", + "offset": "0x38", + "reset": "0x31100002", + "fields": [ + [ + "DIV_CNT", + 0, + 8 + ], + [ + "IDLE_THRES", + 8, + 16 + ], + [ + "MEM_SIZE", + 24, + 4 + ], + [ + "CARRIER_EN", + 28, + 1 + ], + [ + "CARRIER_OUT_LV", + 29, + 1 + ], + [ + "MEM_PD", + 30, + 1 + ], + [ + "CLK_EN", + 31, + 1 + ] + ] + }, + { + "name": "CH4CONF0", + "offset": "0x40", + "reset": "0x31100002", + "fields": [ + [ + "DIV_CNT", + 0, + 8 + ], + [ + "IDLE_THRES", + 8, + 16 + ], + [ + "MEM_SIZE", + 24, + 4 + ], + [ + "CARRIER_EN", + 28, + 1 + ], + [ + "CARRIER_OUT_LV", + 29, + 1 + ], + [ + "MEM_PD", + 30, + 1 + ], + [ + "CLK_EN", + 31, + 1 + ] + ] + }, + { + "name": "CH5CONF0", + "offset": "0x48", + "reset": "0x31100002", + "fields": [ + [ + "DIV_CNT", + 0, + 8 + ], + [ + "IDLE_THRES", + 8, + 16 + ], + [ + "MEM_SIZE", + 24, + 4 + ], + [ + "CARRIER_EN", + 28, + 1 + ], + [ + "CARRIER_OUT_LV", + 29, + 1 + ], + [ + "MEM_PD", + 30, + 1 + ], + [ + "CLK_EN", + 31, + 1 + ] + ] + }, + { + "name": "CH6CONF0", + "offset": "0x50", + "reset": "0x31100002", + "fields": [ + [ + "DIV_CNT", + 0, + 8 + ], + [ + "IDLE_THRES", + 8, + 16 + ], + [ + "MEM_SIZE", + 24, + 4 + ], + [ + "CARRIER_EN", + 28, + 1 + ], + [ + "CARRIER_OUT_LV", + 29, + 1 + ], + [ + "MEM_PD", + 30, + 1 + ], + [ + "CLK_EN", + 31, + 1 + ] + ] + }, + { + "name": "CH7CONF0", + "offset": "0x58", + "reset": "0x31100002", + "fields": [ + [ + "DIV_CNT", + 0, + 8 + ], + [ + "IDLE_THRES", + 8, + 16 + ], + [ + "MEM_SIZE", + 24, + 4 + ], + [ + "CARRIER_EN", + 28, + 1 + ], + [ + "CARRIER_OUT_LV", + 29, + 1 + ], + [ + "MEM_PD", + 30, + 1 + ], + [ + "CLK_EN", + 31, + 1 + ] + ] + }, + { + "name": "CH0CONF1", + "offset": "0x24", + "reset": "0x00000F20", + "fields": [ + [ + "TX_START", + 0, + 1 + ], + [ + "RX_EN", + 1, + 1 + ], + [ + "MEM_WR_RST", + 2, + 1 + ], + [ + "MEM_RD_RST", + 3, + 1 + ], + [ + "APB_MEM_RST", + 4, + 1 + ], + [ + "MEM_OWNER", + 5, + 1 + ], + [ + "TX_CONTI_MODE", + 6, + 1 + ], + [ + "RX_FILTER_EN", + 7, + 1 + ], + [ + "RX_FILTER_THRES", + 8, + 8 + ], + [ + "REF_CNT_RST", + 16, + 1 + ], + [ + "REF_ALWAYS_ON", + 17, + 1 + ], + [ + "IDLE_OUT_LV", + 18, + 1 + ], + [ + "IDLE_OUT_EN", + 19, + 1 + ] + ] + }, + { + "name": "CH1CONF1", + "offset": "0x2C", + "reset": "0x00000F20", + "fields": [ + [ + "TX_START", + 0, + 1 + ], + [ + "RX_EN", + 1, + 1 + ], + [ + "MEM_WR_RST", + 2, + 1 + ], + [ + "MEM_RD_RST", + 3, + 1 + ], + [ + "APB_MEM_RST", + 4, + 1 + ], + [ + "MEM_OWNER", + 5, + 1 + ], + [ + "TX_CONTI_MODE", + 6, + 1 + ], + [ + "RX_FILTER_EN", + 7, + 1 + ], + [ + "RX_FILTER_THRES", + 8, + 8 + ], + [ + "REF_CNT_RST", + 16, + 1 + ], + [ + "REF_ALWAYS_ON", + 17, + 1 + ], + [ + "IDLE_OUT_LV", + 18, + 1 + ], + [ + "IDLE_OUT_EN", + 19, + 1 + ] + ] + }, + { + "name": "CH2CONF1", + "offset": "0x34", + "reset": "0x00000F20", + "fields": [ + [ + "TX_START", + 0, + 1 + ], + [ + "RX_EN", + 1, + 1 + ], + [ + "MEM_WR_RST", + 2, + 1 + ], + [ + "MEM_RD_RST", + 3, + 1 + ], + [ + "APB_MEM_RST", + 4, + 1 + ], + [ + "MEM_OWNER", + 5, + 1 + ], + [ + "TX_CONTI_MODE", + 6, + 1 + ], + [ + "RX_FILTER_EN", + 7, + 1 + ], + [ + "RX_FILTER_THRES", + 8, + 8 + ], + [ + "REF_CNT_RST", + 16, + 1 + ], + [ + "REF_ALWAYS_ON", + 17, + 1 + ], + [ + "IDLE_OUT_LV", + 18, + 1 + ], + [ + "IDLE_OUT_EN", + 19, + 1 + ] + ] + }, + { + "name": "CH3CONF1", + "offset": "0x3C", + "reset": "0x00000F20", + "fields": [ + [ + "TX_START", + 0, + 1 + ], + [ + "RX_EN", + 1, + 1 + ], + [ + "MEM_WR_RST", + 2, + 1 + ], + [ + "MEM_RD_RST", + 3, + 1 + ], + [ + "APB_MEM_RST", + 4, + 1 + ], + [ + "MEM_OWNER", + 5, + 1 + ], + [ + "TX_CONTI_MODE", + 6, + 1 + ], + [ + "RX_FILTER_EN", + 7, + 1 + ], + [ + "RX_FILTER_THRES", + 8, + 8 + ], + [ + "REF_CNT_RST", + 16, + 1 + ], + [ + "REF_ALWAYS_ON", + 17, + 1 + ], + [ + "IDLE_OUT_LV", + 18, + 1 + ], + [ + "IDLE_OUT_EN", + 19, + 1 + ] + ] + }, + { + "name": "CH4CONF1", + "offset": "0x44", + "reset": "0x00000F20", + "fields": [ + [ + "TX_START", + 0, + 1 + ], + [ + "RX_EN", + 1, + 1 + ], + [ + "MEM_WR_RST", + 2, + 1 + ], + [ + "MEM_RD_RST", + 3, + 1 + ], + [ + "APB_MEM_RST", + 4, + 1 + ], + [ + "MEM_OWNER", + 5, + 1 + ], + [ + "TX_CONTI_MODE", + 6, + 1 + ], + [ + "RX_FILTER_EN", + 7, + 1 + ], + [ + "RX_FILTER_THRES", + 8, + 8 + ], + [ + "REF_CNT_RST", + 16, + 1 + ], + [ + "REF_ALWAYS_ON", + 17, + 1 + ], + [ + "IDLE_OUT_LV", + 18, + 1 + ], + [ + "IDLE_OUT_EN", + 19, + 1 + ] + ] + }, + { + "name": "CH5CONF1", + "offset": "0x4C", + "reset": "0x00000F20", + "fields": [ + [ + "TX_START", + 0, + 1 + ], + [ + "RX_EN", + 1, + 1 + ], + [ + "MEM_WR_RST", + 2, + 1 + ], + [ + "MEM_RD_RST", + 3, + 1 + ], + [ + "APB_MEM_RST", + 4, + 1 + ], + [ + "MEM_OWNER", + 5, + 1 + ], + [ + "TX_CONTI_MODE", + 6, + 1 + ], + [ + "RX_FILTER_EN", + 7, + 1 + ], + [ + "RX_FILTER_THRES", + 8, + 8 + ], + [ + "REF_CNT_RST", + 16, + 1 + ], + [ + "REF_ALWAYS_ON", + 17, + 1 + ], + [ + "IDLE_OUT_LV", + 18, + 1 + ], + [ + "IDLE_OUT_EN", + 19, + 1 + ] + ] + }, + { + "name": "CH6CONF1", + "offset": "0x54", + "reset": "0x00000F20", + "fields": [ + [ + "TX_START", + 0, + 1 + ], + [ + "RX_EN", + 1, + 1 + ], + [ + "MEM_WR_RST", + 2, + 1 + ], + [ + "MEM_RD_RST", + 3, + 1 + ], + [ + "APB_MEM_RST", + 4, + 1 + ], + [ + "MEM_OWNER", + 5, + 1 + ], + [ + "TX_CONTI_MODE", + 6, + 1 + ], + [ + "RX_FILTER_EN", + 7, + 1 + ], + [ + "RX_FILTER_THRES", + 8, + 8 + ], + [ + "REF_CNT_RST", + 16, + 1 + ], + [ + "REF_ALWAYS_ON", + 17, + 1 + ], + [ + "IDLE_OUT_LV", + 18, + 1 + ], + [ + "IDLE_OUT_EN", + 19, + 1 + ] + ] + }, + { + "name": "CH7CONF1", + "offset": "0x5C", + "reset": "0x00000F20", + "fields": [ + [ + "TX_START", + 0, + 1 + ], + [ + "RX_EN", + 1, + 1 + ], + [ + "MEM_WR_RST", + 2, + 1 + ], + [ + "MEM_RD_RST", + 3, + 1 + ], + [ + "APB_MEM_RST", + 4, + 1 + ], + [ + "MEM_OWNER", + 5, + 1 + ], + [ + "TX_CONTI_MODE", + 6, + 1 + ], + [ + "RX_FILTER_EN", + 7, + 1 + ], + [ + "RX_FILTER_THRES", + 8, + 8 + ], + [ + "REF_CNT_RST", + 16, + 1 + ], + [ + "REF_ALWAYS_ON", + 17, + 1 + ], + [ + "IDLE_OUT_LV", + 18, + 1 + ], + [ + "IDLE_OUT_EN", + 19, + 1 + ] + ] + }, + { + "name": "CH0STATUS", + "offset": "0x60", + "fields": [ + [ + "STATUS", + 0, + 32, + "r" + ], + [ + "MEM_WADDR_EX", + 0, + 10, + "r" + ], + [ + "MEM_RADDR_EX", + 12, + 10, + "r" + ], + [ + "STATE", + 24, + 3, + "r" + ], + [ + "MEM_OWNER_ERR", + 27, + 1, + "r" + ], + [ + "MEM_FULL", + 28, + 1, + "r" + ], + [ + "MEM_EMPTY", + 29, + 1, + "r" + ], + [ + "APB_MEM_WR_ERR", + 30, + 1, + "r" + ], + [ + "APB_MEM_RD_ERR", + 31, + 1, + "r" + ] + ] + }, + { + "name": "CH1STATUS", + "offset": "0x64", + "fields": [ + [ + "STATUS", + 0, + 32, + "r" + ], + [ + "MEM_WADDR_EX", + 0, + 10, + "r" + ], + [ + "MEM_RADDR_EX", + 12, + 10, + "r" + ], + [ + "STATE", + 24, + 3, + "r" + ], + [ + "MEM_OWNER_ERR", + 27, + 1, + "r" + ], + [ + "MEM_FULL", + 28, + 1, + "r" + ], + [ + "MEM_EMPTY", + 29, + 1, + "r" + ], + [ + "APB_MEM_WR_ERR", + 30, + 1, + "r" + ], + [ + "APB_MEM_RD_ERR", + 31, + 1, + "r" + ] + ] + }, + { + "name": "CH2STATUS", + "offset": "0x68", + "fields": [ + [ + "STATUS", + 0, + 32, + "r" + ], + [ + "MEM_WADDR_EX", + 0, + 10, + "r" + ], + [ + "MEM_RADDR_EX", + 12, + 10, + "r" + ], + [ + "STATE", + 24, + 3, + "r" + ], + [ + "MEM_OWNER_ERR", + 27, + 1, + "r" + ], + [ + "MEM_FULL", + 28, + 1, + "r" + ], + [ + "MEM_EMPTY", + 29, + 1, + "r" + ], + [ + "APB_MEM_WR_ERR", + 30, + 1, + "r" + ], + [ + "APB_MEM_RD_ERR", + 31, + 1, + "r" + ] + ] + }, + { + "name": "CH3STATUS", + "offset": "0x6C", + "fields": [ + [ + "STATUS", + 0, + 32, + "r" + ], + [ + "MEM_WADDR_EX", + 0, + 10, + "r" + ], + [ + "MEM_RADDR_EX", + 12, + 10, + "r" + ], + [ + "STATE", + 24, + 3, + "r" + ], + [ + "MEM_OWNER_ERR", + 27, + 1, + "r" + ], + [ + "MEM_FULL", + 28, + 1, + "r" + ], + [ + "MEM_EMPTY", + 29, + 1, + "r" + ], + [ + "APB_MEM_WR_ERR", + 30, + 1, + "r" + ], + [ + "APB_MEM_RD_ERR", + 31, + 1, + "r" + ] + ] + }, + { + "name": "CH4STATUS", + "offset": "0x70", + "fields": [ + [ + "STATUS", + 0, + 32, + "r" + ], + [ + "MEM_WADDR_EX", + 0, + 10, + "r" + ], + [ + "MEM_RADDR_EX", + 12, + 10, + "r" + ], + [ + "STATE", + 24, + 3, + "r" + ], + [ + "MEM_OWNER_ERR", + 27, + 1, + "r" + ], + [ + "MEM_FULL", + 28, + 1, + "r" + ], + [ + "MEM_EMPTY", + 29, + 1, + "r" + ], + [ + "APB_MEM_WR_ERR", + 30, + 1, + "r" + ], + [ + "APB_MEM_RD_ERR", + 31, + 1, + "r" + ] + ] + }, + { + "name": "CH5STATUS", + "offset": "0x74", + "fields": [ + [ + "STATUS", + 0, + 32, + "r" + ], + [ + "MEM_WADDR_EX", + 0, + 10, + "r" + ], + [ + "MEM_RADDR_EX", + 12, + 10, + "r" + ], + [ + "STATE", + 24, + 3, + "r" + ], + [ + "MEM_OWNER_ERR", + 27, + 1, + "r" + ], + [ + "MEM_FULL", + 28, + 1, + "r" + ], + [ + "MEM_EMPTY", + 29, + 1, + "r" + ], + [ + "APB_MEM_WR_ERR", + 30, + 1, + "r" + ], + [ + "APB_MEM_RD_ERR", + 31, + 1, + "r" + ] + ] + }, + { + "name": "CH6STATUS", + "offset": "0x78", + "fields": [ + [ + "STATUS", + 0, + 32, + "r" + ], + [ + "MEM_WADDR_EX", + 0, + 10, + "r" + ], + [ + "MEM_RADDR_EX", + 12, + 10, + "r" + ], + [ + "STATE", + 24, + 3, + "r" + ], + [ + "MEM_OWNER_ERR", + 27, + 1, + "r" + ], + [ + "MEM_FULL", + 28, + 1, + "r" + ], + [ + "MEM_EMPTY", + 29, + 1, + "r" + ], + [ + "APB_MEM_WR_ERR", + 30, + 1, + "r" + ], + [ + "APB_MEM_RD_ERR", + 31, + 1, + "r" + ] + ] + }, + { + "name": "CH7STATUS", + "offset": "0x7C", + "fields": [ + [ + "STATUS", + 0, + 32, + "r" + ], + [ + "MEM_WADDR_EX", + 0, + 10, + "r" + ], + [ + "MEM_RADDR_EX", + 12, + 10, + "r" + ], + [ + "STATE", + 24, + 3, + "r" + ], + [ + "MEM_OWNER_ERR", + 27, + 1, + "r" + ], + [ + "MEM_FULL", + 28, + 1, + "r" + ], + [ + "MEM_EMPTY", + 29, + 1, + "r" + ], + [ + "APB_MEM_WR_ERR", + 30, + 1, + "r" + ], + [ + "APB_MEM_RD_ERR", + 31, + 1, + "r" + ] + ] + }, + { + "name": "CH0ADDR", + "offset": "0x80", + "fields": [ + [ + "APB_MEM_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "CH1ADDR", + "offset": "0x84", + "fields": [ + [ + "APB_MEM_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "CH2ADDR", + "offset": "0x88", + "fields": [ + [ + "APB_MEM_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "CH3ADDR", + "offset": "0x8C", + "fields": [ + [ + "APB_MEM_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "CH4ADDR", + "offset": "0x90", + "fields": [ + [ + "APB_MEM_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "CH5ADDR", + "offset": "0x94", + "fields": [ + [ + "APB_MEM_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "CH6ADDR", + "offset": "0x98", + "fields": [ + [ + "APB_MEM_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "CH7ADDR", + "offset": "0x9C", + "fields": [ + [ + "APB_MEM_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "INT_RAW", + "offset": "0xA0", + "fields": [ + [ + "CH0_TX_END", + 0, + 1, + "r" + ], + [ + "CH1_TX_END", + 3, + 1, + "r" + ], + [ + "CH2_TX_END", + 6, + 1, + "r" + ], + [ + "CH3_TX_END", + 9, + 1, + "r" + ], + [ + "CH4_TX_END", + 12, + 1, + "r" + ], + [ + "CH5_TX_END", + 15, + 1, + "r" + ], + [ + "CH6_TX_END", + 18, + 1, + "r" + ], + [ + "CH7_TX_END", + 21, + 1, + "r" + ], + [ + "CH0_RX_END", + 1, + 1, + "r" + ], + [ + "CH1_RX_END", + 4, + 1, + "r" + ], + [ + "CH2_RX_END", + 7, + 1, + "r" + ], + [ + "CH3_RX_END", + 10, + 1, + "r" + ], + [ + "CH4_RX_END", + 13, + 1, + "r" + ], + [ + "CH5_RX_END", + 16, + 1, + "r" + ], + [ + "CH6_RX_END", + 19, + 1, + "r" + ], + [ + "CH7_RX_END", + 22, + 1, + "r" + ], + [ + "CH0_ERR", + 2, + 1, + "r" + ], + [ + "CH1_ERR", + 5, + 1, + "r" + ], + [ + "CH2_ERR", + 8, + 1, + "r" + ], + [ + "CH3_ERR", + 11, + 1, + "r" + ], + [ + "CH4_ERR", + 14, + 1, + "r" + ], + [ + "CH5_ERR", + 17, + 1, + "r" + ], + [ + "CH6_ERR", + 20, + 1, + "r" + ], + [ + "CH7_ERR", + 23, + 1, + "r" + ], + [ + "CH0_TX_THR_EVENT", + 24, + 1, + "r" + ], + [ + "CH1_TX_THR_EVENT", + 25, + 1, + "r" + ], + [ + "CH2_TX_THR_EVENT", + 26, + 1, + "r" + ], + [ + "CH3_TX_THR_EVENT", + 27, + 1, + "r" + ], + [ + "CH4_TX_THR_EVENT", + 28, + 1, + "r" + ], + [ + "CH5_TX_THR_EVENT", + 29, + 1, + "r" + ], + [ + "CH6_TX_THR_EVENT", + 30, + 1, + "r" + ], + [ + "CH7_TX_THR_EVENT", + 31, + 1, + "r" + ] + ] + }, + { + "name": "INT_ST", + "offset": "0xA4", + "fields": [ + [ + "CH0_TX_END", + 0, + 1, + "r" + ], + [ + "CH1_TX_END", + 3, + 1, + "r" + ], + [ + "CH2_TX_END", + 6, + 1, + "r" + ], + [ + "CH3_TX_END", + 9, + 1, + "r" + ], + [ + "CH4_TX_END", + 12, + 1, + "r" + ], + [ + "CH5_TX_END", + 15, + 1, + "r" + ], + [ + "CH6_TX_END", + 18, + 1, + "r" + ], + [ + "CH7_TX_END", + 21, + 1, + "r" + ], + [ + "CH0_RX_END", + 1, + 1, + "r" + ], + [ + "CH1_RX_END", + 4, + 1, + "r" + ], + [ + "CH2_RX_END", + 7, + 1, + "r" + ], + [ + "CH3_RX_END", + 10, + 1, + "r" + ], + [ + "CH4_RX_END", + 13, + 1, + "r" + ], + [ + "CH5_RX_END", + 16, + 1, + "r" + ], + [ + "CH6_RX_END", + 19, + 1, + "r" + ], + [ + "CH7_RX_END", + 22, + 1, + "r" + ], + [ + "CH0_ERR", + 2, + 1, + "r" + ], + [ + "CH1_ERR", + 5, + 1, + "r" + ], + [ + "CH2_ERR", + 8, + 1, + "r" + ], + [ + "CH3_ERR", + 11, + 1, + "r" + ], + [ + "CH4_ERR", + 14, + 1, + "r" + ], + [ + "CH5_ERR", + 17, + 1, + "r" + ], + [ + "CH6_ERR", + 20, + 1, + "r" + ], + [ + "CH7_ERR", + 23, + 1, + "r" + ], + [ + "CH0_TX_THR_EVENT", + 24, + 1, + "r" + ], + [ + "CH1_TX_THR_EVENT", + 25, + 1, + "r" + ], + [ + "CH2_TX_THR_EVENT", + 26, + 1, + "r" + ], + [ + "CH3_TX_THR_EVENT", + 27, + 1, + "r" + ], + [ + "CH4_TX_THR_EVENT", + 28, + 1, + "r" + ], + [ + "CH5_TX_THR_EVENT", + 29, + 1, + "r" + ], + [ + "CH6_TX_THR_EVENT", + 30, + 1, + "r" + ], + [ + "CH7_TX_THR_EVENT", + 31, + 1, + "r" + ] + ] + }, + { + "name": "INT_ENA", + "offset": "0xA8", + "fields": [ + [ + "CH0_TX_END", + 0, + 1 + ], + [ + "CH1_TX_END", + 3, + 1 + ], + [ + "CH2_TX_END", + 6, + 1 + ], + [ + "CH3_TX_END", + 9, + 1 + ], + [ + "CH4_TX_END", + 12, + 1 + ], + [ + "CH5_TX_END", + 15, + 1 + ], + [ + "CH6_TX_END", + 18, + 1 + ], + [ + "CH7_TX_END", + 21, + 1 + ], + [ + "CH0_RX_END", + 1, + 1 + ], + [ + "CH1_RX_END", + 4, + 1 + ], + [ + "CH2_RX_END", + 7, + 1 + ], + [ + "CH3_RX_END", + 10, + 1 + ], + [ + "CH4_RX_END", + 13, + 1 + ], + [ + "CH5_RX_END", + 16, + 1 + ], + [ + "CH6_RX_END", + 19, + 1 + ], + [ + "CH7_RX_END", + 22, + 1 + ], + [ + "CH0_ERR", + 2, + 1 + ], + [ + "CH1_ERR", + 5, + 1 + ], + [ + "CH2_ERR", + 8, + 1 + ], + [ + "CH3_ERR", + 11, + 1 + ], + [ + "CH4_ERR", + 14, + 1 + ], + [ + "CH5_ERR", + 17, + 1 + ], + [ + "CH6_ERR", + 20, + 1 + ], + [ + "CH7_ERR", + 23, + 1 + ], + [ + "CH0_TX_THR_EVENT", + 24, + 1 + ], + [ + "CH1_TX_THR_EVENT", + 25, + 1 + ], + [ + "CH2_TX_THR_EVENT", + 26, + 1 + ], + [ + "CH3_TX_THR_EVENT", + 27, + 1 + ], + [ + "CH4_TX_THR_EVENT", + 28, + 1 + ], + [ + "CH5_TX_THR_EVENT", + 29, + 1 + ], + [ + "CH6_TX_THR_EVENT", + 30, + 1 + ], + [ + "CH7_TX_THR_EVENT", + 31, + 1 + ] + ] + }, + { + "name": "INT_CLR", + "offset": "0xAC", + "fields": [ + [ + "CH0_TX_END", + 0, + 1, + "w" + ], + [ + "CH1_TX_END", + 3, + 1, + "w" + ], + [ + "CH2_TX_END", + 6, + 1, + "w" + ], + [ + "CH3_TX_END", + 9, + 1, + "w" + ], + [ + "CH4_TX_END", + 12, + 1, + "w" + ], + [ + "CH5_TX_END", + 15, + 1, + "w" + ], + [ + "CH6_TX_END", + 18, + 1, + "w" + ], + [ + "CH7_TX_END", + 21, + 1, + "w" + ], + [ + "CH0_RX_END", + 1, + 1, + "w" + ], + [ + "CH1_RX_END", + 4, + 1, + "w" + ], + [ + "CH2_RX_END", + 7, + 1, + "w" + ], + [ + "CH3_RX_END", + 10, + 1, + "w" + ], + [ + "CH4_RX_END", + 13, + 1, + "w" + ], + [ + "CH5_RX_END", + 16, + 1, + "w" + ], + [ + "CH6_RX_END", + 19, + 1, + "w" + ], + [ + "CH7_RX_END", + 22, + 1, + "w" + ], + [ + "CH0_ERR", + 2, + 1, + "w" + ], + [ + "CH1_ERR", + 5, + 1, + "w" + ], + [ + "CH2_ERR", + 8, + 1, + "w" + ], + [ + "CH3_ERR", + 11, + 1, + "w" + ], + [ + "CH4_ERR", + 14, + 1, + "w" + ], + [ + "CH5_ERR", + 17, + 1, + "w" + ], + [ + "CH6_ERR", + 20, + 1, + "w" + ], + [ + "CH7_ERR", + 23, + 1, + "w" + ], + [ + "CH0_TX_THR_EVENT", + 24, + 1, + "w" + ], + [ + "CH1_TX_THR_EVENT", + 25, + 1, + "w" + ], + [ + "CH2_TX_THR_EVENT", + 26, + 1, + "w" + ], + [ + "CH3_TX_THR_EVENT", + 27, + 1, + "w" + ], + [ + "CH4_TX_THR_EVENT", + 28, + 1, + "w" + ], + [ + "CH5_TX_THR_EVENT", + 29, + 1, + "w" + ], + [ + "CH6_TX_THR_EVENT", + 30, + 1, + "w" + ], + [ + "CH7_TX_THR_EVENT", + 31, + 1, + "w" + ] + ] + }, + { + "name": "CH0CARRIER_DUTY", + "offset": "0xB0", + "reset": "0x00400040", + "fields": [ + [ + "CARRIER_LOW", + 0, + 16 + ], + [ + "CARRIER_HIGH", + 16, + 16 + ] + ] + }, + { + "name": "CH1CARRIER_DUTY", + "offset": "0xB4", + "reset": "0x00400040", + "fields": [ + [ + "CARRIER_LOW", + 0, + 16 + ], + [ + "CARRIER_HIGH", + 16, + 16 + ] + ] + }, + { + "name": "CH2CARRIER_DUTY", + "offset": "0xB8", + "reset": "0x00400040", + "fields": [ + [ + "CARRIER_LOW", + 0, + 16 + ], + [ + "CARRIER_HIGH", + 16, + 16 + ] + ] + }, + { + "name": "CH3CARRIER_DUTY", + "offset": "0xBC", + "reset": "0x00400040", + "fields": [ + [ + "CARRIER_LOW", + 0, + 16 + ], + [ + "CARRIER_HIGH", + 16, + 16 + ] + ] + }, + { + "name": "CH4CARRIER_DUTY", + "offset": "0xC0", + "reset": "0x00400040", + "fields": [ + [ + "CARRIER_LOW", + 0, + 16 + ], + [ + "CARRIER_HIGH", + 16, + 16 + ] + ] + }, + { + "name": "CH5CARRIER_DUTY", + "offset": "0xC4", + "reset": "0x00400040", + "fields": [ + [ + "CARRIER_LOW", + 0, + 16 + ], + [ + "CARRIER_HIGH", + 16, + 16 + ] + ] + }, + { + "name": "CH6CARRIER_DUTY", + "offset": "0xC8", + "reset": "0x00400040", + "fields": [ + [ + "CARRIER_LOW", + 0, + 16 + ], + [ + "CARRIER_HIGH", + 16, + 16 + ] + ] + }, + { + "name": "CH7CARRIER_DUTY", + "offset": "0xCC", + "reset": "0x00400040", + "fields": [ + [ + "CARRIER_LOW", + 0, + 16 + ], + [ + "CARRIER_HIGH", + 16, + 16 + ] + ] + }, + { + "name": "CH0_TX_LIM", + "offset": "0xD0", + "reset": "0x00000080", + "fields": [ + [ + "TX_LIM", + 0, + 9 + ] + ] + }, + { + "name": "CH1_TX_LIM", + "offset": "0xD4", + "reset": "0x00000080", + "fields": [ + [ + "TX_LIM", + 0, + 9 + ] + ] + }, + { + "name": "CH2_TX_LIM", + "offset": "0xD8", + "reset": "0x00000080", + "fields": [ + [ + "TX_LIM", + 0, + 9 + ] + ] + }, + { + "name": "CH3_TX_LIM", + "offset": "0xDC", + "reset": "0x00000080", + "fields": [ + [ + "TX_LIM", + 0, + 9 + ] + ] + }, + { + "name": "CH4_TX_LIM", + "offset": "0xE0", + "reset": "0x00000080", + "fields": [ + [ + "TX_LIM", + 0, + 9 + ] + ] + }, + { + "name": "CH5_TX_LIM", + "offset": "0xE4", + "reset": "0x00000080", + "fields": [ + [ + "TX_LIM", + 0, + 9 + ] + ] + }, + { + "name": "CH6_TX_LIM", + "offset": "0xE8", + "reset": "0x00000080", + "fields": [ + [ + "TX_LIM", + 0, + 9 + ] + ] + }, + { + "name": "CH7_TX_LIM", + "offset": "0xEC", + "reset": "0x00000080", + "fields": [ + [ + "TX_LIM", + 0, + 9 + ] + ] + }, + { + "name": "APB_CONF", + "offset": "0xF0", + "fields": [ + [ + "APB_FIFO_MASK", + 0, + 1 + ], + [ + "MEM_TX_WRAP_EN", + 1, + 1 + ] + ] + }, + { + "name": "DATE", + "offset": "0xFC", + "reset": "0x16022600", + "fields": [ + [ + "DATE", + 0, + 32 + ] + ] + } + ] + }, + { + "name": "PCNT", + "base": "0x3FF57000", + "interrupts": [ + "PCNT" + ], + "registers": [ + { + "name": "U0_CONF0", + "offset": "0x00", + "reset": "0x00003C10", + "fields": [ + [ + "FILTER_THRES", + 0, + 10 + ], + [ + "FILTER_EN", + 10, + 1 + ], + [ + "THR_ZERO_EN", + 11, + 1 + ], + [ + "THR_H_LIM_EN", + 12, + 1 + ], + [ + "THR_L_LIM_EN", + 13, + 1 + ], + [ + "THR_THRES0_EN", + 14, + 1 + ], + [ + "THR_THRES1_EN", + 15, + 1 + ], + [ + "CH0_NEG_MODE", + 16, + 2 + ], + [ + "CH0_POS_MODE", + 18, + 2 + ], + [ + "CH0_HCTRL_MODE", + 20, + 2 + ], + [ + "CH0_LCTRL_MODE", + 22, + 2 + ], + [ + "CH1_NEG_MODE", + 24, + 2 + ], + [ + "CH1_POS_MODE", + 26, + 2 + ], + [ + "CH1_HCTRL_MODE", + 28, + 2 + ], + [ + "CH1_LCTRL_MODE", + 30, + 2 + ] + ] + }, + { + "name": "U1_CONF0", + "offset": "0x0C", + "reset": "0x00003C10", + "fields": [ + [ + "FILTER_THRES", + 0, + 10 + ], + [ + "FILTER_EN", + 10, + 1 + ], + [ + "THR_ZERO_EN", + 11, + 1 + ], + [ + "THR_H_LIM_EN", + 12, + 1 + ], + [ + "THR_L_LIM_EN", + 13, + 1 + ], + [ + "THR_THRES0_EN", + 14, + 1 + ], + [ + "THR_THRES1_EN", + 15, + 1 + ], + [ + "CH0_NEG_MODE", + 16, + 2 + ], + [ + "CH0_POS_MODE", + 18, + 2 + ], + [ + "CH0_HCTRL_MODE", + 20, + 2 + ], + [ + "CH0_LCTRL_MODE", + 22, + 2 + ], + [ + "CH1_NEG_MODE", + 24, + 2 + ], + [ + "CH1_POS_MODE", + 26, + 2 + ], + [ + "CH1_HCTRL_MODE", + 28, + 2 + ], + [ + "CH1_LCTRL_MODE", + 30, + 2 + ] + ] + }, + { + "name": "U2_CONF0", + "offset": "0x18", + "reset": "0x00003C10", + "fields": [ + [ + "FILTER_THRES", + 0, + 10 + ], + [ + "FILTER_EN", + 10, + 1 + ], + [ + "THR_ZERO_EN", + 11, + 1 + ], + [ + "THR_H_LIM_EN", + 12, + 1 + ], + [ + "THR_L_LIM_EN", + 13, + 1 + ], + [ + "THR_THRES0_EN", + 14, + 1 + ], + [ + "THR_THRES1_EN", + 15, + 1 + ], + [ + "CH0_NEG_MODE", + 16, + 2 + ], + [ + "CH0_POS_MODE", + 18, + 2 + ], + [ + "CH0_HCTRL_MODE", + 20, + 2 + ], + [ + "CH0_LCTRL_MODE", + 22, + 2 + ], + [ + "CH1_NEG_MODE", + 24, + 2 + ], + [ + "CH1_POS_MODE", + 26, + 2 + ], + [ + "CH1_HCTRL_MODE", + 28, + 2 + ], + [ + "CH1_LCTRL_MODE", + 30, + 2 + ] + ] + }, + { + "name": "U3_CONF0", + "offset": "0x24", + "reset": "0x00003C10", + "fields": [ + [ + "FILTER_THRES", + 0, + 10 + ], + [ + "FILTER_EN", + 10, + 1 + ], + [ + "THR_ZERO_EN", + 11, + 1 + ], + [ + "THR_H_LIM_EN", + 12, + 1 + ], + [ + "THR_L_LIM_EN", + 13, + 1 + ], + [ + "THR_THRES0_EN", + 14, + 1 + ], + [ + "THR_THRES1_EN", + 15, + 1 + ], + [ + "CH0_NEG_MODE", + 16, + 2 + ], + [ + "CH0_POS_MODE", + 18, + 2 + ], + [ + "CH0_HCTRL_MODE", + 20, + 2 + ], + [ + "CH0_LCTRL_MODE", + 22, + 2 + ], + [ + "CH1_NEG_MODE", + 24, + 2 + ], + [ + "CH1_POS_MODE", + 26, + 2 + ], + [ + "CH1_HCTRL_MODE", + 28, + 2 + ], + [ + "CH1_LCTRL_MODE", + 30, + 2 + ] + ] + }, + { + "name": "U4_CONF0", + "offset": "0x30", + "reset": "0x00003C10", + "fields": [ + [ + "FILTER_THRES", + 0, + 10 + ], + [ + "FILTER_EN", + 10, + 1 + ], + [ + "THR_ZERO_EN", + 11, + 1 + ], + [ + "THR_H_LIM_EN", + 12, + 1 + ], + [ + "THR_L_LIM_EN", + 13, + 1 + ], + [ + "THR_THRES0_EN", + 14, + 1 + ], + [ + "THR_THRES1_EN", + 15, + 1 + ], + [ + "CH0_NEG_MODE", + 16, + 2 + ], + [ + "CH0_POS_MODE", + 18, + 2 + ], + [ + "CH0_HCTRL_MODE", + 20, + 2 + ], + [ + "CH0_LCTRL_MODE", + 22, + 2 + ], + [ + "CH1_NEG_MODE", + 24, + 2 + ], + [ + "CH1_POS_MODE", + 26, + 2 + ], + [ + "CH1_HCTRL_MODE", + 28, + 2 + ], + [ + "CH1_LCTRL_MODE", + 30, + 2 + ] + ] + }, + { + "name": "U5_CONF0", + "offset": "0x3C", + "reset": "0x00003C10", + "fields": [ + [ + "FILTER_THRES", + 0, + 10 + ], + [ + "FILTER_EN", + 10, + 1 + ], + [ + "THR_ZERO_EN", + 11, + 1 + ], + [ + "THR_H_LIM_EN", + 12, + 1 + ], + [ + "THR_L_LIM_EN", + 13, + 1 + ], + [ + "THR_THRES0_EN", + 14, + 1 + ], + [ + "THR_THRES1_EN", + 15, + 1 + ], + [ + "CH0_NEG_MODE", + 16, + 2 + ], + [ + "CH0_POS_MODE", + 18, + 2 + ], + [ + "CH0_HCTRL_MODE", + 20, + 2 + ], + [ + "CH0_LCTRL_MODE", + 22, + 2 + ], + [ + "CH1_NEG_MODE", + 24, + 2 + ], + [ + "CH1_POS_MODE", + 26, + 2 + ], + [ + "CH1_HCTRL_MODE", + 28, + 2 + ], + [ + "CH1_LCTRL_MODE", + 30, + 2 + ] + ] + }, + { + "name": "U6_CONF0", + "offset": "0x48", + "reset": "0x00003C10", + "fields": [ + [ + "FILTER_THRES", + 0, + 10 + ], + [ + "FILTER_EN", + 10, + 1 + ], + [ + "THR_ZERO_EN", + 11, + 1 + ], + [ + "THR_H_LIM_EN", + 12, + 1 + ], + [ + "THR_L_LIM_EN", + 13, + 1 + ], + [ + "THR_THRES0_EN", + 14, + 1 + ], + [ + "THR_THRES1_EN", + 15, + 1 + ], + [ + "CH0_NEG_MODE", + 16, + 2 + ], + [ + "CH0_POS_MODE", + 18, + 2 + ], + [ + "CH0_HCTRL_MODE", + 20, + 2 + ], + [ + "CH0_LCTRL_MODE", + 22, + 2 + ], + [ + "CH1_NEG_MODE", + 24, + 2 + ], + [ + "CH1_POS_MODE", + 26, + 2 + ], + [ + "CH1_HCTRL_MODE", + 28, + 2 + ], + [ + "CH1_LCTRL_MODE", + 30, + 2 + ] + ] + }, + { + "name": "U7_CONF0", + "offset": "0x54", + "reset": "0x00003C10", + "fields": [ + [ + "FILTER_THRES", + 0, + 10 + ], + [ + "FILTER_EN", + 10, + 1 + ], + [ + "THR_ZERO_EN", + 11, + 1 + ], + [ + "THR_H_LIM_EN", + 12, + 1 + ], + [ + "THR_L_LIM_EN", + 13, + 1 + ], + [ + "THR_THRES0_EN", + 14, + 1 + ], + [ + "THR_THRES1_EN", + 15, + 1 + ], + [ + "CH0_NEG_MODE", + 16, + 2 + ], + [ + "CH0_POS_MODE", + 18, + 2 + ], + [ + "CH0_HCTRL_MODE", + 20, + 2 + ], + [ + "CH0_LCTRL_MODE", + 22, + 2 + ], + [ + "CH1_NEG_MODE", + 24, + 2 + ], + [ + "CH1_POS_MODE", + 26, + 2 + ], + [ + "CH1_HCTRL_MODE", + 28, + 2 + ], + [ + "CH1_LCTRL_MODE", + 30, + 2 + ] + ] + }, + { + "name": "U0_CONF1", + "offset": "0x04", + "fields": [ + [ + "CNT_THRES0", + 0, + 16 + ], + [ + "CNT_THRES1", + 16, + 16 + ] + ] + }, + { + "name": "U1_CONF1", + "offset": "0x10", + "fields": [ + [ + "CNT_THRES0", + 0, + 16 + ], + [ + "CNT_THRES1", + 16, + 16 + ] + ] + }, + { + "name": "U2_CONF1", + "offset": "0x1C", + "fields": [ + [ + "CNT_THRES0", + 0, + 16 + ], + [ + "CNT_THRES1", + 16, + 16 + ] + ] + }, + { + "name": "U3_CONF1", + "offset": "0x28", + "fields": [ + [ + "CNT_THRES0", + 0, + 16 + ], + [ + "CNT_THRES1", + 16, + 16 + ] + ] + }, + { + "name": "U4_CONF1", + "offset": "0x34", + "fields": [ + [ + "CNT_THRES0", + 0, + 16 + ], + [ + "CNT_THRES1", + 16, + 16 + ] + ] + }, + { + "name": "U5_CONF1", + "offset": "0x40", + "fields": [ + [ + "CNT_THRES0", + 0, + 16 + ], + [ + "CNT_THRES1", + 16, + 16 + ] + ] + }, + { + "name": "U6_CONF1", + "offset": "0x4C", + "fields": [ + [ + "CNT_THRES0", + 0, + 16 + ], + [ + "CNT_THRES1", + 16, + 16 + ] + ] + }, + { + "name": "U7_CONF1", + "offset": "0x58", + "fields": [ + [ + "CNT_THRES0", + 0, + 16 + ], + [ + "CNT_THRES1", + 16, + 16 + ] + ] + }, + { + "name": "U0_CONF2", + "offset": "0x08", + "fields": [ + [ + "CNT_H_LIM", + 0, + 16 + ], + [ + "CNT_L_LIM", + 16, + 16 + ] + ] + }, + { + "name": "U1_CONF2", + "offset": "0x14", + "fields": [ + [ + "CNT_H_LIM", + 0, + 16 + ], + [ + "CNT_L_LIM", + 16, + 16 + ] + ] + }, + { + "name": "U2_CONF2", + "offset": "0x20", + "fields": [ + [ + "CNT_H_LIM", + 0, + 16 + ], + [ + "CNT_L_LIM", + 16, + 16 + ] + ] + }, + { + "name": "U3_CONF2", + "offset": "0x2C", + "fields": [ + [ + "CNT_H_LIM", + 0, + 16 + ], + [ + "CNT_L_LIM", + 16, + 16 + ] + ] + }, + { + "name": "U4_CONF2", + "offset": "0x38", + "fields": [ + [ + "CNT_H_LIM", + 0, + 16 + ], + [ + "CNT_L_LIM", + 16, + 16 + ] + ] + }, + { + "name": "U5_CONF2", + "offset": "0x44", + "fields": [ + [ + "CNT_H_LIM", + 0, + 16 + ], + [ + "CNT_L_LIM", + 16, + 16 + ] + ] + }, + { + "name": "U6_CONF2", + "offset": "0x50", + "fields": [ + [ + "CNT_H_LIM", + 0, + 16 + ], + [ + "CNT_L_LIM", + 16, + 16 + ] + ] + }, + { + "name": "U7_CONF2", + "offset": "0x5C", + "fields": [ + [ + "CNT_H_LIM", + 0, + 16 + ], + [ + "CNT_L_LIM", + 16, + 16 + ] + ] + }, + { + "name": "U0_CNT", + "offset": "0x60", + "fields": [ + [ + "CNT", + 0, + 16, + "r" + ] + ] + }, + { + "name": "U1_CNT", + "offset": "0x64", + "fields": [ + [ + "CNT", + 0, + 16, + "r" + ] + ] + }, + { + "name": "U2_CNT", + "offset": "0x68", + "fields": [ + [ + "CNT", + 0, + 16, + "r" + ] + ] + }, + { + "name": "U3_CNT", + "offset": "0x6C", + "fields": [ + [ + "CNT", + 0, + 16, + "r" + ] + ] + }, + { + "name": "U4_CNT", + "offset": "0x70", + "fields": [ + [ + "CNT", + 0, + 16, + "r" + ] + ] + }, + { + "name": "U5_CNT", + "offset": "0x74", + "fields": [ + [ + "CNT", + 0, + 16, + "r" + ] + ] + }, + { + "name": "U6_CNT", + "offset": "0x78", + "fields": [ + [ + "CNT", + 0, + 16, + "r" + ] + ] + }, + { + "name": "U7_CNT", + "offset": "0x7C", + "fields": [ + [ + "CNT", + 0, + 16, + "r" + ] + ] + }, + { + "name": "INT_RAW", + "offset": "0x80", + "fields": [ + [ + "CNT_THR_EVENT_U0", + 0, + 1, + "r" + ], + [ + "CNT_THR_EVENT_U1", + 1, + 1, + "r" + ], + [ + "CNT_THR_EVENT_U2", + 2, + 1, + "r" + ], + [ + "CNT_THR_EVENT_U3", + 3, + 1, + "r" + ], + [ + "CNT_THR_EVENT_U4", + 4, + 1, + "r" + ], + [ + "CNT_THR_EVENT_U5", + 5, + 1, + "r" + ], + [ + "CNT_THR_EVENT_U6", + 6, + 1, + "r" + ], + [ + "CNT_THR_EVENT_U7", + 7, + 1, + "r" + ] + ] + }, + { + "name": "INT_ST", + "offset": "0x84", + "fields": [ + [ + "CNT_THR_EVENT_U0", + 0, + 1, + "r" + ], + [ + "CNT_THR_EVENT_U1", + 1, + 1, + "r" + ], + [ + "CNT_THR_EVENT_U2", + 2, + 1, + "r" + ], + [ + "CNT_THR_EVENT_U3", + 3, + 1, + "r" + ], + [ + "CNT_THR_EVENT_U4", + 4, + 1, + "r" + ], + [ + "CNT_THR_EVENT_U5", + 5, + 1, + "r" + ], + [ + "CNT_THR_EVENT_U6", + 6, + 1, + "r" + ], + [ + "CNT_THR_EVENT_U7", + 7, + 1, + "r" + ] + ] + }, + { + "name": "INT_ENA", + "offset": "0x88", + "fields": [ + [ + "CNT_THR_EVENT_U0", + 0, + 1 + ], + [ + "CNT_THR_EVENT_U1", + 1, + 1 + ], + [ + "CNT_THR_EVENT_U2", + 2, + 1 + ], + [ + "CNT_THR_EVENT_U3", + 3, + 1 + ], + [ + "CNT_THR_EVENT_U4", + 4, + 1 + ], + [ + "CNT_THR_EVENT_U5", + 5, + 1 + ], + [ + "CNT_THR_EVENT_U6", + 6, + 1 + ], + [ + "CNT_THR_EVENT_U7", + 7, + 1 + ] + ] + }, + { + "name": "INT_CLR", + "offset": "0x8C", + "fields": [ + [ + "CNT_THR_EVENT_U0", + 0, + 1, + "w" + ], + [ + "CNT_THR_EVENT_U1", + 1, + 1, + "w" + ], + [ + "CNT_THR_EVENT_U2", + 2, + 1, + "w" + ], + [ + "CNT_THR_EVENT_U3", + 3, + 1, + "w" + ], + [ + "CNT_THR_EVENT_U4", + 4, + 1, + "w" + ], + [ + "CNT_THR_EVENT_U5", + 5, + 1, + "w" + ], + [ + "CNT_THR_EVENT_U6", + 6, + 1, + "w" + ], + [ + "CNT_THR_EVENT_U7", + 7, + 1, + "w" + ] + ] + }, + { + "name": "U0_STATUS", + "offset": "0x90", + "fields": [ + [ + "CORE_STATUS_U0", + 0, + 32, + "r" + ], + [ + "ZERO_MODE", + 0, + 2 + ], + [ + "THRES1", + 2, + 1 + ], + [ + "THRES0", + 3, + 1 + ], + [ + "L_LIM", + 4, + 1 + ], + [ + "H_LIM", + 5, + 1 + ], + [ + "ZERO", + 6, + 1 + ] + ] + }, + { + "name": "U1_STATUS", + "offset": "0x94", + "fields": [ + [ + "CORE_STATUS_U0", + 0, + 32, + "r" + ], + [ + "ZERO_MODE", + 0, + 2 + ], + [ + "THRES1", + 2, + 1 + ], + [ + "THRES0", + 3, + 1 + ], + [ + "L_LIM", + 4, + 1 + ], + [ + "H_LIM", + 5, + 1 + ], + [ + "ZERO", + 6, + 1 + ] + ] + }, + { + "name": "U2_STATUS", + "offset": "0x98", + "fields": [ + [ + "CORE_STATUS_U0", + 0, + 32, + "r" + ], + [ + "ZERO_MODE", + 0, + 2 + ], + [ + "THRES1", + 2, + 1 + ], + [ + "THRES0", + 3, + 1 + ], + [ + "L_LIM", + 4, + 1 + ], + [ + "H_LIM", + 5, + 1 + ], + [ + "ZERO", + 6, + 1 + ] + ] + }, + { + "name": "U3_STATUS", + "offset": "0x9C", + "fields": [ + [ + "CORE_STATUS_U0", + 0, + 32, + "r" + ], + [ + "ZERO_MODE", + 0, + 2 + ], + [ + "THRES1", + 2, + 1 + ], + [ + "THRES0", + 3, + 1 + ], + [ + "L_LIM", + 4, + 1 + ], + [ + "H_LIM", + 5, + 1 + ], + [ + "ZERO", + 6, + 1 + ] + ] + }, + { + "name": "U4_STATUS", + "offset": "0xA0", + "fields": [ + [ + "CORE_STATUS_U0", + 0, + 32, + "r" + ], + [ + "ZERO_MODE", + 0, + 2 + ], + [ + "THRES1", + 2, + 1 + ], + [ + "THRES0", + 3, + 1 + ], + [ + "L_LIM", + 4, + 1 + ], + [ + "H_LIM", + 5, + 1 + ], + [ + "ZERO", + 6, + 1 + ] + ] + }, + { + "name": "U5_STATUS", + "offset": "0xA4", + "fields": [ + [ + "CORE_STATUS_U0", + 0, + 32, + "r" + ], + [ + "ZERO_MODE", + 0, + 2 + ], + [ + "THRES1", + 2, + 1 + ], + [ + "THRES0", + 3, + 1 + ], + [ + "L_LIM", + 4, + 1 + ], + [ + "H_LIM", + 5, + 1 + ], + [ + "ZERO", + 6, + 1 + ] + ] + }, + { + "name": "U6_STATUS", + "offset": "0xA8", + "fields": [ + [ + "CORE_STATUS_U0", + 0, + 32, + "r" + ], + [ + "ZERO_MODE", + 0, + 2 + ], + [ + "THRES1", + 2, + 1 + ], + [ + "THRES0", + 3, + 1 + ], + [ + "L_LIM", + 4, + 1 + ], + [ + "H_LIM", + 5, + 1 + ], + [ + "ZERO", + 6, + 1 + ] + ] + }, + { + "name": "U7_STATUS", + "offset": "0xAC", + "fields": [ + [ + "CORE_STATUS_U0", + 0, + 32, + "r" + ], + [ + "ZERO_MODE", + 0, + 2 + ], + [ + "THRES1", + 2, + 1 + ], + [ + "THRES0", + 3, + 1 + ], + [ + "L_LIM", + 4, + 1 + ], + [ + "H_LIM", + 5, + 1 + ], + [ + "ZERO", + 6, + 1 + ] + ] + }, + { + "name": "CTRL", + "offset": "0xB0", + "reset": "0x00005555", + "fields": [ + [ + "CNT_RST_U0", + 0, + 1 + ], + [ + "CNT_PAUSE_U0", + 1, + 1 + ], + [ + "CNT_RST_U1", + 2, + 1 + ], + [ + "CNT_PAUSE_U1", + 3, + 1 + ], + [ + "CNT_RST_U2", + 4, + 1 + ], + [ + "CNT_PAUSE_U2", + 5, + 1 + ], + [ + "CNT_RST_U3", + 6, + 1 + ], + [ + "CNT_PAUSE_U3", + 7, + 1 + ], + [ + "CNT_RST_U4", + 8, + 1 + ], + [ + "CNT_PAUSE_U4", + 9, + 1 + ], + [ + "CNT_RST_U5", + 10, + 1 + ], + [ + "CNT_PAUSE_U5", + 11, + 1 + ], + [ + "CNT_RST_U6", + 12, + 1 + ], + [ + "CNT_PAUSE_U6", + 13, + 1 + ], + [ + "CNT_RST_U7", + 14, + 1 + ], + [ + "CNT_PAUSE_U7", + 15, + 1 + ], + [ + "CLK_EN", + 16, + 1 + ] + ] + }, + { + "name": "DATE", + "offset": "0xFC", + "reset": "0x14122600", + "fields": [ + [ + "DATE", + 0, + 32 + ] + ] + } + ] + }, + { + "name": "SLC", + "base": "0x3FF58000", + "registers": [ + { + "name": "CONF0", + "offset": "0x00", + "reset": "0xFF3CFF30", + "fields": [ + [ + "SLC0_TX_RST", + 0, + 1 + ], + [ + "SLC0_RX_RST", + 1, + 1 + ], + [ + "AHBM_FIFO_RST", + 2, + 1 + ], + [ + "AHBM_RST", + 3, + 1 + ], + [ + "SLC0_TX_LOOP_TEST", + 4, + 1 + ], + [ + "SLC0_RX_LOOP_TEST", + 5, + 1 + ], + [ + "SLC0_RX_AUTO_WRBACK", + 6, + 1 + ], + [ + "SLC0_RX_NO_RESTART_CLR", + 7, + 1 + ], + [ + "SLC0_RXDSCR_BURST_EN", + 8, + 1 + ], + [ + "SLC0_RXDATA_BURST_EN", + 9, + 1 + ], + [ + "SLC0_RXLINK_AUTO_RET", + 10, + 1 + ], + [ + "SLC0_TXLINK_AUTO_RET", + 11, + 1 + ], + [ + "SLC0_TXDSCR_BURST_EN", + 12, + 1 + ], + [ + "SLC0_TXDATA_BURST_EN", + 13, + 1 + ], + [ + "SLC0_TOKEN_AUTO_CLR", + 14, + 1 + ], + [ + "SLC0_TOKEN_SEL", + 15, + 1 + ], + [ + "SLC1_TX_RST", + 16, + 1 + ], + [ + "SLC1_RX_RST", + 17, + 1 + ], + [ + "SLC0_WR_RETRY_MASK_EN", + 18, + 1 + ], + [ + "SLC1_WR_RETRY_MASK_EN", + 19, + 1 + ], + [ + "SLC1_TX_LOOP_TEST", + 20, + 1 + ], + [ + "SLC1_RX_LOOP_TEST", + 21, + 1 + ], + [ + "SLC1_RX_AUTO_WRBACK", + 22, + 1 + ], + [ + "SLC1_RX_NO_RESTART_CLR", + 23, + 1 + ], + [ + "SLC1_RXDSCR_BURST_EN", + 24, + 1 + ], + [ + "SLC1_RXDATA_BURST_EN", + 25, + 1 + ], + [ + "SLC1_RXLINK_AUTO_RET", + 26, + 1 + ], + [ + "SLC1_TXLINK_AUTO_RET", + 27, + 1 + ], + [ + "SLC1_TXDSCR_BURST_EN", + 28, + 1 + ], + [ + "SLC1_TXDATA_BURST_EN", + 29, + 1 + ], + [ + "SLC1_TOKEN_AUTO_CLR", + 30, + 1 + ], + [ + "SLC1_TOKEN_SEL", + 31, + 1 + ] + ] + }, + { + "name": "_0INT_RAW", + "offset": "0x04", + "fields": [ + [ + "FRHOST_BIT0_INT_RAW", + 0, + 1, + "r" + ], + [ + "FRHOST_BIT1_INT_RAW", + 1, + 1, + "r" + ], + [ + "FRHOST_BIT2_INT_RAW", + 2, + 1, + "r" + ], + [ + "FRHOST_BIT3_INT_RAW", + 3, + 1, + "r" + ], + [ + "FRHOST_BIT4_INT_RAW", + 4, + 1, + "r" + ], + [ + "FRHOST_BIT5_INT_RAW", + 5, + 1, + "r" + ], + [ + "FRHOST_BIT6_INT_RAW", + 6, + 1, + "r" + ], + [ + "FRHOST_BIT7_INT_RAW", + 7, + 1, + "r" + ], + [ + "SLC0_RX_START_INT_RAW", + 8, + 1, + "r" + ], + [ + "SLC0_TX_START_INT_RAW", + 9, + 1, + "r" + ], + [ + "SLC0_RX_UDF_INT_RAW", + 10, + 1, + "r" + ], + [ + "SLC0_TX_OVF_INT_RAW", + 11, + 1, + "r" + ], + [ + "SLC0_TOKEN0_1TO0_INT_RAW", + 12, + 1, + "r" + ], + [ + "SLC0_TOKEN1_1TO0_INT_RAW", + 13, + 1, + "r" + ], + [ + "SLC0_TX_DONE_INT_RAW", + 14, + 1, + "r" + ], + [ + "SLC0_TX_SUC_EOF_INT_RAW", + 15, + 1, + "r" + ], + [ + "SLC0_RX_DONE_INT_RAW", + 16, + 1, + "r" + ], + [ + "SLC0_RX_EOF_INT_RAW", + 17, + 1, + "r" + ], + [ + "SLC0_TOHOST_INT_RAW", + 18, + 1, + "r" + ], + [ + "SLC0_TX_DSCR_ERR_INT_RAW", + 19, + 1, + "r" + ], + [ + "SLC0_RX_DSCR_ERR_INT_RAW", + 20, + 1, + "r" + ], + [ + "SLC0_TX_DSCR_EMPTY_INT_RAW", + 21, + 1, + "r" + ], + [ + "SLC0_HOST_RD_ACK_INT_RAW", + 22, + 1, + "r" + ], + [ + "SLC0_WR_RETRY_DONE_INT_RAW", + 23, + 1, + "r" + ], + [ + "SLC0_TX_ERR_EOF_INT_RAW", + 24, + 1, + "r" + ], + [ + "CMD_DTC_INT_RAW", + 25, + 1, + "r" + ], + [ + "SLC0_RX_QUICK_EOF_INT_RAW", + 26, + 1, + "r" + ] + ] + }, + { + "name": "_0INT_ST", + "offset": "0x08", + "fields": [ + [ + "FRHOST_BIT0_INT_ST", + 0, + 1, + "r" + ], + [ + "FRHOST_BIT1_INT_ST", + 1, + 1, + "r" + ], + [ + "FRHOST_BIT2_INT_ST", + 2, + 1, + "r" + ], + [ + "FRHOST_BIT3_INT_ST", + 3, + 1, + "r" + ], + [ + "FRHOST_BIT4_INT_ST", + 4, + 1, + "r" + ], + [ + "FRHOST_BIT5_INT_ST", + 5, + 1, + "r" + ], + [ + "FRHOST_BIT6_INT_ST", + 6, + 1, + "r" + ], + [ + "FRHOST_BIT7_INT_ST", + 7, + 1, + "r" + ], + [ + "SLC0_RX_START_INT_ST", + 8, + 1, + "r" + ], + [ + "SLC0_TX_START_INT_ST", + 9, + 1, + "r" + ], + [ + "SLC0_RX_UDF_INT_ST", + 10, + 1, + "r" + ], + [ + "SLC0_TX_OVF_INT_ST", + 11, + 1, + "r" + ], + [ + "SLC0_TOKEN0_1TO0_INT_ST", + 12, + 1, + "r" + ], + [ + "SLC0_TOKEN1_1TO0_INT_ST", + 13, + 1, + "r" + ], + [ + "SLC0_TX_DONE_INT_ST", + 14, + 1, + "r" + ], + [ + "SLC0_TX_SUC_EOF_INT_ST", + 15, + 1, + "r" + ], + [ + "SLC0_RX_DONE_INT_ST", + 16, + 1, + "r" + ], + [ + "SLC0_RX_EOF_INT_ST", + 17, + 1, + "r" + ], + [ + "SLC0_TOHOST_INT_ST", + 18, + 1, + "r" + ], + [ + "SLC0_TX_DSCR_ERR_INT_ST", + 19, + 1, + "r" + ], + [ + "SLC0_RX_DSCR_ERR_INT_ST", + 20, + 1, + "r" + ], + [ + "SLC0_TX_DSCR_EMPTY_INT_ST", + 21, + 1, + "r" + ], + [ + "SLC0_HOST_RD_ACK_INT_ST", + 22, + 1, + "r" + ], + [ + "SLC0_WR_RETRY_DONE_INT_ST", + 23, + 1, + "r" + ], + [ + "SLC0_TX_ERR_EOF_INT_ST", + 24, + 1, + "r" + ], + [ + "CMD_DTC_INT_ST", + 25, + 1, + "r" + ], + [ + "SLC0_RX_QUICK_EOF_INT_ST", + 26, + 1, + "r" + ] + ] + }, + { + "name": "_0INT_ENA", + "offset": "0x0C", + "fields": [ + [ + "FRHOST_BIT0_INT_ENA", + 0, + 1 + ], + [ + "FRHOST_BIT1_INT_ENA", + 1, + 1 + ], + [ + "FRHOST_BIT2_INT_ENA", + 2, + 1 + ], + [ + "FRHOST_BIT3_INT_ENA", + 3, + 1 + ], + [ + "FRHOST_BIT4_INT_ENA", + 4, + 1 + ], + [ + "FRHOST_BIT5_INT_ENA", + 5, + 1 + ], + [ + "FRHOST_BIT6_INT_ENA", + 6, + 1 + ], + [ + "FRHOST_BIT7_INT_ENA", + 7, + 1 + ], + [ + "SLC0_RX_START_INT_ENA", + 8, + 1 + ], + [ + "SLC0_TX_START_INT_ENA", + 9, + 1 + ], + [ + "SLC0_RX_UDF_INT_ENA", + 10, + 1 + ], + [ + "SLC0_TX_OVF_INT_ENA", + 11, + 1 + ], + [ + "SLC0_TOKEN0_1TO0_INT_ENA", + 12, + 1 + ], + [ + "SLC0_TOKEN1_1TO0_INT_ENA", + 13, + 1 + ], + [ + "SLC0_TX_DONE_INT_ENA", + 14, + 1 + ], + [ + "SLC0_TX_SUC_EOF_INT_ENA", + 15, + 1 + ], + [ + "SLC0_RX_DONE_INT_ENA", + 16, + 1 + ], + [ + "SLC0_RX_EOF_INT_ENA", + 17, + 1 + ], + [ + "SLC0_TOHOST_INT_ENA", + 18, + 1 + ], + [ + "SLC0_TX_DSCR_ERR_INT_ENA", + 19, + 1 + ], + [ + "SLC0_RX_DSCR_ERR_INT_ENA", + 20, + 1 + ], + [ + "SLC0_TX_DSCR_EMPTY_INT_ENA", + 21, + 1 + ], + [ + "SLC0_HOST_RD_ACK_INT_ENA", + 22, + 1 + ], + [ + "SLC0_WR_RETRY_DONE_INT_ENA", + 23, + 1 + ], + [ + "SLC0_TX_ERR_EOF_INT_ENA", + 24, + 1 + ], + [ + "CMD_DTC_INT_ENA", + 25, + 1 + ], + [ + "SLC0_RX_QUICK_EOF_INT_ENA", + 26, + 1 + ] + ] + }, + { + "name": "_0INT_CLR", + "offset": "0x10", + "fields": [ + [ + "FRHOST_BIT0_INT_CLR", + 0, + 1, + "w" + ], + [ + "FRHOST_BIT1_INT_CLR", + 1, + 1, + "w" + ], + [ + "FRHOST_BIT2_INT_CLR", + 2, + 1, + "w" + ], + [ + "FRHOST_BIT3_INT_CLR", + 3, + 1, + "w" + ], + [ + "FRHOST_BIT4_INT_CLR", + 4, + 1, + "w" + ], + [ + "FRHOST_BIT5_INT_CLR", + 5, + 1, + "w" + ], + [ + "FRHOST_BIT6_INT_CLR", + 6, + 1, + "w" + ], + [ + "FRHOST_BIT7_INT_CLR", + 7, + 1, + "w" + ], + [ + "SLC0_RX_START_INT_CLR", + 8, + 1, + "w" + ], + [ + "SLC0_TX_START_INT_CLR", + 9, + 1, + "w" + ], + [ + "SLC0_RX_UDF_INT_CLR", + 10, + 1, + "w" + ], + [ + "SLC0_TX_OVF_INT_CLR", + 11, + 1, + "w" + ], + [ + "SLC0_TOKEN0_1TO0_INT_CLR", + 12, + 1, + "w" + ], + [ + "SLC0_TOKEN1_1TO0_INT_CLR", + 13, + 1, + "w" + ], + [ + "SLC0_TX_DONE_INT_CLR", + 14, + 1, + "w" + ], + [ + "SLC0_TX_SUC_EOF_INT_CLR", + 15, + 1, + "w" + ], + [ + "SLC0_RX_DONE_INT_CLR", + 16, + 1, + "w" + ], + [ + "SLC0_RX_EOF_INT_CLR", + 17, + 1, + "w" + ], + [ + "SLC0_TOHOST_INT_CLR", + 18, + 1, + "w" + ], + [ + "SLC0_TX_DSCR_ERR_INT_CLR", + 19, + 1, + "w" + ], + [ + "SLC0_RX_DSCR_ERR_INT_CLR", + 20, + 1, + "w" + ], + [ + "SLC0_TX_DSCR_EMPTY_INT_CLR", + 21, + 1, + "w" + ], + [ + "SLC0_HOST_RD_ACK_INT_CLR", + 22, + 1, + "w" + ], + [ + "SLC0_WR_RETRY_DONE_INT_CLR", + 23, + 1, + "w" + ], + [ + "SLC0_TX_ERR_EOF_INT_CLR", + 24, + 1, + "w" + ], + [ + "CMD_DTC_INT_CLR", + 25, + 1, + "w" + ], + [ + "SLC0_RX_QUICK_EOF_INT_CLR", + 26, + 1, + "w" + ] + ] + }, + { + "name": "_1INT_RAW", + "offset": "0x14", + "fields": [ + [ + "FRHOST_BIT8_INT_RAW", + 0, + 1, + "r" + ], + [ + "FRHOST_BIT9_INT_RAW", + 1, + 1, + "r" + ], + [ + "FRHOST_BIT10_INT_RAW", + 2, + 1, + "r" + ], + [ + "FRHOST_BIT11_INT_RAW", + 3, + 1, + "r" + ], + [ + "FRHOST_BIT12_INT_RAW", + 4, + 1, + "r" + ], + [ + "FRHOST_BIT13_INT_RAW", + 5, + 1, + "r" + ], + [ + "FRHOST_BIT14_INT_RAW", + 6, + 1, + "r" + ], + [ + "FRHOST_BIT15_INT_RAW", + 7, + 1, + "r" + ], + [ + "SLC1_RX_START_INT_RAW", + 8, + 1, + "r" + ], + [ + "SLC1_TX_START_INT_RAW", + 9, + 1, + "r" + ], + [ + "SLC1_RX_UDF_INT_RAW", + 10, + 1, + "r" + ], + [ + "SLC1_TX_OVF_INT_RAW", + 11, + 1, + "r" + ], + [ + "SLC1_TOKEN0_1TO0_INT_RAW", + 12, + 1, + "r" + ], + [ + "SLC1_TOKEN1_1TO0_INT_RAW", + 13, + 1, + "r" + ], + [ + "SLC1_TX_DONE_INT_RAW", + 14, + 1, + "r" + ], + [ + "SLC1_TX_SUC_EOF_INT_RAW", + 15, + 1, + "r" + ], + [ + "SLC1_RX_DONE_INT_RAW", + 16, + 1, + "r" + ], + [ + "SLC1_RX_EOF_INT_RAW", + 17, + 1, + "r" + ], + [ + "SLC1_TOHOST_INT_RAW", + 18, + 1, + "r" + ], + [ + "SLC1_TX_DSCR_ERR_INT_RAW", + 19, + 1, + "r" + ], + [ + "SLC1_RX_DSCR_ERR_INT_RAW", + 20, + 1, + "r" + ], + [ + "SLC1_TX_DSCR_EMPTY_INT_RAW", + 21, + 1, + "r" + ], + [ + "SLC1_HOST_RD_ACK_INT_RAW", + 22, + 1, + "r" + ], + [ + "SLC1_WR_RETRY_DONE_INT_RAW", + 23, + 1, + "r" + ], + [ + "SLC1_TX_ERR_EOF_INT_RAW", + 24, + 1, + "r" + ] + ] + }, + { + "name": "_1INT_ST", + "offset": "0x18", + "fields": [ + [ + "FRHOST_BIT8_INT_ST", + 0, + 1, + "r" + ], + [ + "FRHOST_BIT9_INT_ST", + 1, + 1, + "r" + ], + [ + "FRHOST_BIT10_INT_ST", + 2, + 1, + "r" + ], + [ + "FRHOST_BIT11_INT_ST", + 3, + 1, + "r" + ], + [ + "FRHOST_BIT12_INT_ST", + 4, + 1, + "r" + ], + [ + "FRHOST_BIT13_INT_ST", + 5, + 1, + "r" + ], + [ + "FRHOST_BIT14_INT_ST", + 6, + 1, + "r" + ], + [ + "FRHOST_BIT15_INT_ST", + 7, + 1, + "r" + ], + [ + "SLC1_RX_START_INT_ST", + 8, + 1, + "r" + ], + [ + "SLC1_TX_START_INT_ST", + 9, + 1, + "r" + ], + [ + "SLC1_RX_UDF_INT_ST", + 10, + 1, + "r" + ], + [ + "SLC1_TX_OVF_INT_ST", + 11, + 1, + "r" + ], + [ + "SLC1_TOKEN0_1TO0_INT_ST", + 12, + 1, + "r" + ], + [ + "SLC1_TOKEN1_1TO0_INT_ST", + 13, + 1, + "r" + ], + [ + "SLC1_TX_DONE_INT_ST", + 14, + 1, + "r" + ], + [ + "SLC1_TX_SUC_EOF_INT_ST", + 15, + 1, + "r" + ], + [ + "SLC1_RX_DONE_INT_ST", + 16, + 1, + "r" + ], + [ + "SLC1_RX_EOF_INT_ST", + 17, + 1, + "r" + ], + [ + "SLC1_TOHOST_INT_ST", + 18, + 1, + "r" + ], + [ + "SLC1_TX_DSCR_ERR_INT_ST", + 19, + 1, + "r" + ], + [ + "SLC1_RX_DSCR_ERR_INT_ST", + 20, + 1, + "r" + ], + [ + "SLC1_TX_DSCR_EMPTY_INT_ST", + 21, + 1, + "r" + ], + [ + "SLC1_HOST_RD_ACK_INT_ST", + 22, + 1, + "r" + ], + [ + "SLC1_WR_RETRY_DONE_INT_ST", + 23, + 1, + "r" + ], + [ + "SLC1_TX_ERR_EOF_INT_ST", + 24, + 1, + "r" + ] + ] + }, + { + "name": "_1INT_ENA", + "offset": "0x1C", + "fields": [ + [ + "FRHOST_BIT8_INT_ENA", + 0, + 1 + ], + [ + "FRHOST_BIT9_INT_ENA", + 1, + 1 + ], + [ + "FRHOST_BIT10_INT_ENA", + 2, + 1 + ], + [ + "FRHOST_BIT11_INT_ENA", + 3, + 1 + ], + [ + "FRHOST_BIT12_INT_ENA", + 4, + 1 + ], + [ + "FRHOST_BIT13_INT_ENA", + 5, + 1 + ], + [ + "FRHOST_BIT14_INT_ENA", + 6, + 1 + ], + [ + "FRHOST_BIT15_INT_ENA", + 7, + 1 + ], + [ + "SLC1_RX_START_INT_ENA", + 8, + 1 + ], + [ + "SLC1_TX_START_INT_ENA", + 9, + 1 + ], + [ + "SLC1_RX_UDF_INT_ENA", + 10, + 1 + ], + [ + "SLC1_TX_OVF_INT_ENA", + 11, + 1 + ], + [ + "SLC1_TOKEN0_1TO0_INT_ENA", + 12, + 1 + ], + [ + "SLC1_TOKEN1_1TO0_INT_ENA", + 13, + 1 + ], + [ + "SLC1_TX_DONE_INT_ENA", + 14, + 1 + ], + [ + "SLC1_TX_SUC_EOF_INT_ENA", + 15, + 1 + ], + [ + "SLC1_RX_DONE_INT_ENA", + 16, + 1 + ], + [ + "SLC1_RX_EOF_INT_ENA", + 17, + 1 + ], + [ + "SLC1_TOHOST_INT_ENA", + 18, + 1 + ], + [ + "SLC1_TX_DSCR_ERR_INT_ENA", + 19, + 1 + ], + [ + "SLC1_RX_DSCR_ERR_INT_ENA", + 20, + 1 + ], + [ + "SLC1_TX_DSCR_EMPTY_INT_ENA", + 21, + 1 + ], + [ + "SLC1_HOST_RD_ACK_INT_ENA", + 22, + 1 + ], + [ + "SLC1_WR_RETRY_DONE_INT_ENA", + 23, + 1 + ], + [ + "SLC1_TX_ERR_EOF_INT_ENA", + 24, + 1 + ] + ] + }, + { + "name": "_1INT_CLR", + "offset": "0x20", + "fields": [ + [ + "FRHOST_BIT8_INT_CLR", + 0, + 1, + "w" + ], + [ + "FRHOST_BIT9_INT_CLR", + 1, + 1, + "w" + ], + [ + "FRHOST_BIT10_INT_CLR", + 2, + 1, + "w" + ], + [ + "FRHOST_BIT11_INT_CLR", + 3, + 1, + "w" + ], + [ + "FRHOST_BIT12_INT_CLR", + 4, + 1, + "w" + ], + [ + "FRHOST_BIT13_INT_CLR", + 5, + 1, + "w" + ], + [ + "FRHOST_BIT14_INT_CLR", + 6, + 1, + "w" + ], + [ + "FRHOST_BIT15_INT_CLR", + 7, + 1, + "w" + ], + [ + "SLC1_RX_START_INT_CLR", + 8, + 1, + "w" + ], + [ + "SLC1_TX_START_INT_CLR", + 9, + 1, + "w" + ], + [ + "SLC1_RX_UDF_INT_CLR", + 10, + 1, + "w" + ], + [ + "SLC1_TX_OVF_INT_CLR", + 11, + 1, + "w" + ], + [ + "SLC1_TOKEN0_1TO0_INT_CLR", + 12, + 1, + "w" + ], + [ + "SLC1_TOKEN1_1TO0_INT_CLR", + 13, + 1, + "w" + ], + [ + "SLC1_TX_DONE_INT_CLR", + 14, + 1, + "w" + ], + [ + "SLC1_TX_SUC_EOF_INT_CLR", + 15, + 1, + "w" + ], + [ + "SLC1_RX_DONE_INT_CLR", + 16, + 1, + "w" + ], + [ + "SLC1_RX_EOF_INT_CLR", + 17, + 1, + "w" + ], + [ + "SLC1_TOHOST_INT_CLR", + 18, + 1, + "w" + ], + [ + "SLC1_TX_DSCR_ERR_INT_CLR", + 19, + 1, + "w" + ], + [ + "SLC1_RX_DSCR_ERR_INT_CLR", + 20, + 1, + "w" + ], + [ + "SLC1_TX_DSCR_EMPTY_INT_CLR", + 21, + 1, + "w" + ], + [ + "SLC1_HOST_RD_ACK_INT_CLR", + 22, + 1, + "w" + ], + [ + "SLC1_WR_RETRY_DONE_INT_CLR", + 23, + 1, + "w" + ], + [ + "SLC1_TX_ERR_EOF_INT_CLR", + 24, + 1, + "w" + ] + ] + }, + { + "name": "RX_STATUS", + "offset": "0x24", + "reset": "0x00020002", + "fields": [ + [ + "SLC0_RX_FULL", + 0, + 1, + "r" + ], + [ + "SLC0_RX_EMPTY", + 1, + 1, + "r" + ], + [ + "SLC1_RX_FULL", + 16, + 1, + "r" + ], + [ + "SLC1_RX_EMPTY", + 17, + 1, + "r" + ] + ] + }, + { + "name": "_0RXFIFO_PUSH", + "offset": "0x28", + "fields": [ + [ + "SLC0_RXFIFO_WDATA", + 0, + 9 + ], + [ + "SLC0_RXFIFO_PUSH", + 16, + 1 + ] + ] + }, + { + "name": "_1RXFIFO_PUSH", + "offset": "0x2C", + "fields": [ + [ + "SLC1_RXFIFO_WDATA", + 0, + 9 + ], + [ + "SLC1_RXFIFO_PUSH", + 16, + 1 + ] + ] + }, + { + "name": "TX_STATUS", + "offset": "0x30", + "reset": "0x00020002", + "fields": [ + [ + "SLC0_TX_FULL", + 0, + 1, + "r" + ], + [ + "SLC0_TX_EMPTY", + 1, + 1, + "r" + ], + [ + "SLC1_TX_FULL", + 16, + 1, + "r" + ], + [ + "SLC1_TX_EMPTY", + 17, + 1, + "r" + ] + ] + }, + { + "name": "_0TXFIFO_POP", + "offset": "0x34", + "fields": [ + [ + "SLC0_TXFIFO_RDATA", + 0, + 11, + "r" + ], + [ + "SLC0_TXFIFO_POP", + 16, + 1 + ] + ] + }, + { + "name": "_1TXFIFO_POP", + "offset": "0x38", + "fields": [ + [ + "SLC1_TXFIFO_RDATA", + 0, + 11, + "r" + ], + [ + "SLC1_TXFIFO_POP", + 16, + 1 + ] + ] + }, + { + "name": "_0RX_LINK", + "offset": "0x3C", + "fields": [ + [ + "SLC0_RXLINK_ADDR", + 0, + 20 + ], + [ + "SLC0_RXLINK_STOP", + 28, + 1 + ], + [ + "SLC0_RXLINK_START", + 29, + 1 + ], + [ + "SLC0_RXLINK_RESTART", + 30, + 1 + ], + [ + "SLC0_RXLINK_PARK", + 31, + 1, + "r" + ] + ] + }, + { + "name": "_0TX_LINK", + "offset": "0x40", + "fields": [ + [ + "SLC0_TXLINK_ADDR", + 0, + 20 + ], + [ + "SLC0_TXLINK_STOP", + 28, + 1 + ], + [ + "SLC0_TXLINK_START", + 29, + 1 + ], + [ + "SLC0_TXLINK_RESTART", + 30, + 1 + ], + [ + "SLC0_TXLINK_PARK", + 31, + 1, + "r" + ] + ] + }, + { + "name": "_1RX_LINK", + "offset": "0x44", + "reset": "0x00100000", + "fields": [ + [ + "SLC1_RXLINK_ADDR", + 0, + 20 + ], + [ + "SLC1_BT_PACKET", + 20, + 1 + ], + [ + "SLC1_RXLINK_STOP", + 28, + 1 + ], + [ + "SLC1_RXLINK_START", + 29, + 1 + ], + [ + "SLC1_RXLINK_RESTART", + 30, + 1 + ], + [ + "SLC1_RXLINK_PARK", + 31, + 1, + "r" + ] + ] + }, + { + "name": "_1TX_LINK", + "offset": "0x48", + "fields": [ + [ + "SLC1_TXLINK_ADDR", + 0, + 20 + ], + [ + "SLC1_TXLINK_STOP", + 28, + 1 + ], + [ + "SLC1_TXLINK_START", + 29, + 1 + ], + [ + "SLC1_TXLINK_RESTART", + 30, + 1 + ], + [ + "SLC1_TXLINK_PARK", + 31, + 1, + "r" + ] + ] + }, + { + "name": "INTVEC_TOHOST", + "offset": "0x4C", + "fields": [ + [ + "SLC0_TOHOST_INTVEC", + 0, + 8, + "w" + ], + [ + "SLC1_TOHOST_INTVEC", + 16, + 8, + "w" + ] + ] + }, + { + "name": "_0TOKEN0", + "offset": "0x50", + "fields": [ + [ + "SLC0_TOKEN0_WDATA", + 0, + 12, + "w" + ], + [ + "SLC0_TOKEN0_WR", + 12, + 1, + "w" + ], + [ + "SLC0_TOKEN0_INC", + 13, + 1, + "w" + ], + [ + "SLC0_TOKEN0_INC_MORE", + 14, + 1, + "w" + ], + [ + "SLC0_TOKEN0", + 16, + 12, + "r" + ] + ] + }, + { + "name": "_0TOKEN1", + "offset": "0x54", + "fields": [ + [ + "SLC0_TOKEN1_WDATA", + 0, + 12, + "w" + ], + [ + "SLC0_TOKEN1_WR", + 12, + 1, + "w" + ], + [ + "SLC0_TOKEN1_INC", + 13, + 1, + "w" + ], + [ + "SLC0_TOKEN1_INC_MORE", + 14, + 1, + "w" + ], + [ + "SLC0_TOKEN1", + 16, + 12, + "r" + ] + ] + }, + { + "name": "_1TOKEN0", + "offset": "0x58", + "fields": [ + [ + "SLC1_TOKEN0_WDATA", + 0, + 12, + "w" + ], + [ + "SLC1_TOKEN0_WR", + 12, + 1, + "w" + ], + [ + "SLC1_TOKEN0_INC", + 13, + 1, + "w" + ], + [ + "SLC1_TOKEN0_INC_MORE", + 14, + 1, + "w" + ], + [ + "SLC1_TOKEN0", + 16, + 12, + "r" + ] + ] + }, + { + "name": "_1TOKEN1", + "offset": "0x5C", + "fields": [ + [ + "SLC1_TOKEN1_WDATA", + 0, + 12, + "w" + ], + [ + "SLC1_TOKEN1_WR", + 12, + 1, + "w" + ], + [ + "SLC1_TOKEN1_INC", + 13, + 1, + "w" + ], + [ + "SLC1_TOKEN1_INC_MORE", + 14, + 1, + "w" + ], + [ + "SLC1_TOKEN1", + 16, + 12, + "r" + ] + ] + }, + { + "name": "CONF1", + "offset": "0x60", + "reset": "0x00300078", + "fields": [ + [ + "SLC0_CHECK_OWNER", + 0, + 1 + ], + [ + "SLC0_TX_CHECK_SUM_EN", + 1, + 1 + ], + [ + "SLC0_RX_CHECK_SUM_EN", + 2, + 1 + ], + [ + "CMD_HOLD_EN", + 3, + 1 + ], + [ + "SLC0_LEN_AUTO_CLR", + 4, + 1 + ], + [ + "SLC0_TX_STITCH_EN", + 5, + 1 + ], + [ + "SLC0_RX_STITCH_EN", + 6, + 1 + ], + [ + "SLC1_CHECK_OWNER", + 16, + 1 + ], + [ + "SLC1_TX_CHECK_SUM_EN", + 17, + 1 + ], + [ + "SLC1_RX_CHECK_SUM_EN", + 18, + 1 + ], + [ + "HOST_INT_LEVEL_SEL", + 19, + 1 + ], + [ + "SLC1_TX_STITCH_EN", + 20, + 1 + ], + [ + "SLC1_RX_STITCH_EN", + 21, + 1 + ], + [ + "CLK_EN", + 22, + 1 + ] + ] + }, + { + "name": "_0_STATE0", + "offset": "0x64", + "fields": [ + [ + "SLC0_STATE0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_STATE1", + "offset": "0x68", + "fields": [ + [ + "SLC0_STATE1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_1_STATE0", + "offset": "0x6C", + "fields": [ + [ + "SLC1_STATE0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_1_STATE1", + "offset": "0x70", + "fields": [ + [ + "SLC1_STATE1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BRIDGE_CONF", + "offset": "0x74", + "reset": "0x000A7720", + "fields": [ + [ + "TXEOF_ENA", + 0, + 6 + ], + [ + "FIFO_MAP_ENA", + 8, + 4 + ], + [ + "SLC0_TX_DUMMY_MODE", + 12, + 1 + ], + [ + "HDA_MAP_128K", + 13, + 1 + ], + [ + "SLC1_TX_DUMMY_MODE", + 14, + 1 + ], + [ + "TX_PUSH_IDLE_NUM", + 16, + 16 + ] + ] + }, + { + "name": "_0_TO_EOF_DES_ADDR", + "offset": "0x78", + "fields": [ + [ + "SLC0_TO_EOF_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_TX_EOF_DES_ADDR", + "offset": "0x7C", + "fields": [ + [ + "SLC0_TX_SUC_EOF_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_TO_EOF_BFR_DES_ADDR", + "offset": "0x80", + "fields": [ + [ + "SLC0_TO_EOF_BFR_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_1_TO_EOF_DES_ADDR", + "offset": "0x84", + "fields": [ + [ + "SLC1_TO_EOF_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_1_TX_EOF_DES_ADDR", + "offset": "0x88", + "fields": [ + [ + "SLC1_TX_SUC_EOF_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_1_TO_EOF_BFR_DES_ADDR", + "offset": "0x8C", + "fields": [ + [ + "SLC1_TO_EOF_BFR_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "AHB_TEST", + "offset": "0x90", + "fields": [ + [ + "AHB_TESTMODE", + 0, + 3 + ], + [ + "AHB_TESTADDR", + 4, + 2 + ] + ] + }, + { + "name": "SDIO_ST", + "offset": "0x94", + "fields": [ + [ + "CMD_ST", + 0, + 3, + "r" + ], + [ + "FUNC_ST", + 4, + 4, + "r" + ], + [ + "SDIO_WAKEUP", + 8, + 1, + "r" + ], + [ + "BUS_ST", + 12, + 3, + "r" + ], + [ + "FUNC1_ACC_STATE", + 16, + 5, + "r" + ], + [ + "FUNC2_ACC_STATE", + 24, + 5, + "r" + ] + ] + }, + { + "name": "RX_DSCR_CONF", + "offset": "0x98", + "reset": "0x101B101A", + "fields": [ + [ + "SLC0_TOKEN_NO_REPLACE", + 0, + 1 + ], + [ + "SLC0_INFOR_NO_REPLACE", + 1, + 1 + ], + [ + "SLC0_RX_FILL_MODE", + 2, + 1 + ], + [ + "SLC0_RX_EOF_MODE", + 3, + 1 + ], + [ + "SLC0_RX_FILL_EN", + 4, + 1 + ], + [ + "SLC0_RD_RETRY_THRESHOLD", + 5, + 11 + ], + [ + "SLC1_TOKEN_NO_REPLACE", + 16, + 1 + ], + [ + "SLC1_INFOR_NO_REPLACE", + 17, + 1 + ], + [ + "SLC1_RX_FILL_MODE", + 18, + 1 + ], + [ + "SLC1_RX_EOF_MODE", + 19, + 1 + ], + [ + "SLC1_RX_FILL_EN", + 20, + 1 + ], + [ + "SLC1_RD_RETRY_THRESHOLD", + 21, + 11 + ] + ] + }, + { + "name": "_0_TXLINK_DSCR", + "offset": "0x9C", + "fields": [ + [ + "SLC0_TXLINK_DSCR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_TXLINK_DSCR_BF0", + "offset": "0xA0", + "fields": [ + [ + "SLC0_TXLINK_DSCR_BF0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_TXLINK_DSCR_BF1", + "offset": "0xA4", + "fields": [ + [ + "SLC0_TXLINK_DSCR_BF1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_RXLINK_DSCR", + "offset": "0xA8", + "fields": [ + [ + "SLC0_RXLINK_DSCR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_RXLINK_DSCR_BF0", + "offset": "0xAC", + "fields": [ + [ + "SLC0_RXLINK_DSCR_BF0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_RXLINK_DSCR_BF1", + "offset": "0xB0", + "fields": [ + [ + "SLC0_RXLINK_DSCR_BF1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_1_TXLINK_DSCR", + "offset": "0xB4", + "fields": [ + [ + "SLC1_TXLINK_DSCR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_1_TXLINK_DSCR_BF0", + "offset": "0xB8", + "fields": [ + [ + "SLC1_TXLINK_DSCR_BF0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_1_TXLINK_DSCR_BF1", + "offset": "0xBC", + "fields": [ + [ + "SLC1_TXLINK_DSCR_BF1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_1_RXLINK_DSCR", + "offset": "0xC0", + "fields": [ + [ + "SLC1_RXLINK_DSCR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_1_RXLINK_DSCR_BF0", + "offset": "0xC4", + "fields": [ + [ + "SLC1_RXLINK_DSCR_BF0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_1_RXLINK_DSCR_BF1", + "offset": "0xC8", + "fields": [ + [ + "SLC1_RXLINK_DSCR_BF1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_TX_ERREOF_DES_ADDR", + "offset": "0xCC", + "fields": [ + [ + "SLC0_TX_ERR_EOF_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_1_TX_ERREOF_DES_ADDR", + "offset": "0xD0", + "fields": [ + [ + "SLC1_TX_ERR_EOF_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "TOKEN_LAT", + "offset": "0xD4", + "fields": [ + [ + "SLC0_TOKEN", + 0, + 12, + "r" + ], + [ + "SLC1_TOKEN", + 16, + 12, + "r" + ] + ] + }, + { + "name": "TX_DSCR_CONF", + "offset": "0xD8", + "reset": "0x00000080", + "fields": [ + [ + "WR_RETRY_THRESHOLD", + 0, + 11 + ] + ] + }, + { + "name": "CMD_INFOR0", + "offset": "0xDC", + "fields": [ + [ + "CMD_CONTENT0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "CMD_INFOR1", + "offset": "0xE0", + "fields": [ + [ + "CMD_CONTENT1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_LEN_CONF", + "offset": "0xE4", + "fields": [ + [ + "SLC0_LEN_WDATA", + 0, + 20, + "w" + ], + [ + "SLC0_LEN_WR", + 20, + 1, + "w" + ], + [ + "SLC0_LEN_INC", + 21, + 1, + "w" + ], + [ + "SLC0_LEN_INC_MORE", + 22, + 1, + "w" + ], + [ + "SLC0_RX_PACKET_LOAD_EN", + 23, + 1 + ], + [ + "SLC0_TX_PACKET_LOAD_EN", + 24, + 1 + ], + [ + "SLC0_RX_GET_USED_DSCR", + 25, + 1, + "w" + ], + [ + "SLC0_TX_GET_USED_DSCR", + 26, + 1, + "w" + ], + [ + "SLC0_RX_NEW_PKT_IND", + 27, + 1, + "r" + ], + [ + "SLC0_TX_NEW_PKT_IND", + 28, + 1, + "r" + ] + ] + }, + { + "name": "_0_LENGTH", + "offset": "0xE8", + "fields": [ + [ + "SLC0_LEN", + 0, + 20, + "r" + ] + ] + }, + { + "name": "_0_TXPKT_H_DSCR", + "offset": "0xEC", + "fields": [ + [ + "SLC0_TX_PKT_H_DSCR_ADDR", + 0, + 32 + ] + ] + }, + { + "name": "_0_TXPKT_E_DSCR", + "offset": "0xF0", + "fields": [ + [ + "SLC0_TX_PKT_E_DSCR_ADDR", + 0, + 32 + ] + ] + }, + { + "name": "_0_RXPKT_H_DSCR", + "offset": "0xF4", + "fields": [ + [ + "SLC0_RX_PKT_H_DSCR_ADDR", + 0, + 32 + ] + ] + }, + { + "name": "_0_RXPKT_E_DSCR", + "offset": "0xF8", + "fields": [ + [ + "SLC0_RX_PKT_E_DSCR_ADDR", + 0, + 32 + ] + ] + }, + { + "name": "_0_TXPKTU_H_DSCR", + "offset": "0xFC", + "fields": [ + [ + "SLC0_TX_PKT_START_DSCR_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_TXPKTU_E_DSCR", + "offset": "0x100", + "fields": [ + [ + "SLC0_TX_PKT_END_DSCR_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_RXPKTU_H_DSCR", + "offset": "0x104", + "fields": [ + [ + "SLC0_RX_PKT_START_DSCR_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_RXPKTU_E_DSCR", + "offset": "0x108", + "fields": [ + [ + "SLC0_RX_PKT_END_DSCR_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "SEQ_POSITION", + "offset": "0x114", + "reset": "0x00000509", + "fields": [ + [ + "SLC0_SEQ_POSITION", + 0, + 8 + ], + [ + "SLC1_SEQ_POSITION", + 8, + 8 + ] + ] + }, + { + "name": "_0_DSCR_REC_CONF", + "offset": "0x118", + "reset": "0x000003FF", + "fields": [ + [ + "SLC0_RX_DSCR_REC_LIM", + 0, + 10 + ] + ] + }, + { + "name": "SDIO_CRC_ST0", + "offset": "0x11C", + "fields": [ + [ + "DAT0_CRC_ERR_CNT", + 0, + 8, + "r" + ], + [ + "DAT1_CRC_ERR_CNT", + 8, + 8, + "r" + ], + [ + "DAT2_CRC_ERR_CNT", + 16, + 8, + "r" + ], + [ + "DAT3_CRC_ERR_CNT", + 24, + 8, + "r" + ] + ] + }, + { + "name": "SDIO_CRC_ST1", + "offset": "0x120", + "fields": [ + [ + "CMD_CRC_ERR_CNT", + 0, + 8, + "r" + ], + [ + "ERR_CNT_CLR", + 31, + 1 + ] + ] + }, + { + "name": "_0_EOF_START_DES", + "offset": "0x124", + "fields": [ + [ + "SLC0_EOF_START_DES_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_PUSH_DSCR_ADDR", + "offset": "0x128", + "fields": [ + [ + "SLC0_RX_PUSH_DSCR_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_DONE_DSCR_ADDR", + "offset": "0x12C", + "fields": [ + [ + "SLC0_RX_DONE_DSCR_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_SUB_START_DES", + "offset": "0x130", + "fields": [ + [ + "SLC0_SUB_PAC_START_DSCR_ADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "_0_DSCR_CNT", + "offset": "0x134", + "fields": [ + [ + "SLC0_RX_DSCR_CNT_LAT", + 0, + 10, + "r" + ], + [ + "SLC0_RX_GET_EOF_OCC", + 16, + 1, + "r" + ] + ] + }, + { + "name": "_0_LEN_LIM_CONF", + "offset": "0x138", + "reset": "0x00005400", + "fields": [ + [ + "SLC0_LEN_LIM", + 0, + 20 + ] + ] + }, + { + "name": "_0INT_ST1", + "offset": "0x13C", + "fields": [ + [ + "FRHOST_BIT0_INT_ST1", + 0, + 1, + "r" + ], + [ + "FRHOST_BIT1_INT_ST1", + 1, + 1, + "r" + ], + [ + "FRHOST_BIT2_INT_ST1", + 2, + 1, + "r" + ], + [ + "FRHOST_BIT3_INT_ST1", + 3, + 1, + "r" + ], + [ + "FRHOST_BIT4_INT_ST1", + 4, + 1, + "r" + ], + [ + "FRHOST_BIT5_INT_ST1", + 5, + 1, + "r" + ], + [ + "FRHOST_BIT6_INT_ST1", + 6, + 1, + "r" + ], + [ + "FRHOST_BIT7_INT_ST1", + 7, + 1, + "r" + ], + [ + "SLC0_RX_START_INT_ST1", + 8, + 1, + "r" + ], + [ + "SLC0_TX_START_INT_ST1", + 9, + 1, + "r" + ], + [ + "SLC0_RX_UDF_INT_ST1", + 10, + 1, + "r" + ], + [ + "SLC0_TX_OVF_INT_ST1", + 11, + 1, + "r" + ], + [ + "SLC0_TOKEN0_1TO0_INT_ST1", + 12, + 1, + "r" + ], + [ + "SLC0_TOKEN1_1TO0_INT_ST1", + 13, + 1, + "r" + ], + [ + "SLC0_TX_DONE_INT_ST1", + 14, + 1, + "r" + ], + [ + "SLC0_TX_SUC_EOF_INT_ST1", + 15, + 1, + "r" + ], + [ + "SLC0_RX_DONE_INT_ST1", + 16, + 1, + "r" + ], + [ + "SLC0_RX_EOF_INT_ST1", + 17, + 1, + "r" + ], + [ + "SLC0_TOHOST_INT_ST1", + 18, + 1, + "r" + ], + [ + "SLC0_TX_DSCR_ERR_INT_ST1", + 19, + 1, + "r" + ], + [ + "SLC0_RX_DSCR_ERR_INT_ST1", + 20, + 1, + "r" + ], + [ + "SLC0_TX_DSCR_EMPTY_INT_ST1", + 21, + 1, + "r" + ], + [ + "SLC0_HOST_RD_ACK_INT_ST1", + 22, + 1, + "r" + ], + [ + "SLC0_WR_RETRY_DONE_INT_ST1", + 23, + 1, + "r" + ], + [ + "SLC0_TX_ERR_EOF_INT_ST1", + 24, + 1, + "r" + ], + [ + "CMD_DTC_INT_ST1", + 25, + 1, + "r" + ], + [ + "SLC0_RX_QUICK_EOF_INT_ST1", + 26, + 1, + "r" + ] + ] + }, + { + "name": "_0INT_ENA1", + "offset": "0x140", + "fields": [ + [ + "FRHOST_BIT0_INT_ENA1", + 0, + 1 + ], + [ + "FRHOST_BIT1_INT_ENA1", + 1, + 1 + ], + [ + "FRHOST_BIT2_INT_ENA1", + 2, + 1 + ], + [ + "FRHOST_BIT3_INT_ENA1", + 3, + 1 + ], + [ + "FRHOST_BIT4_INT_ENA1", + 4, + 1 + ], + [ + "FRHOST_BIT5_INT_ENA1", + 5, + 1 + ], + [ + "FRHOST_BIT6_INT_ENA1", + 6, + 1 + ], + [ + "FRHOST_BIT7_INT_ENA1", + 7, + 1 + ], + [ + "SLC0_RX_START_INT_ENA1", + 8, + 1 + ], + [ + "SLC0_TX_START_INT_ENA1", + 9, + 1 + ], + [ + "SLC0_RX_UDF_INT_ENA1", + 10, + 1 + ], + [ + "SLC0_TX_OVF_INT_ENA1", + 11, + 1 + ], + [ + "SLC0_TOKEN0_1TO0_INT_ENA1", + 12, + 1 + ], + [ + "SLC0_TOKEN1_1TO0_INT_ENA1", + 13, + 1 + ], + [ + "SLC0_TX_DONE_INT_ENA1", + 14, + 1 + ], + [ + "SLC0_TX_SUC_EOF_INT_ENA1", + 15, + 1 + ], + [ + "SLC0_RX_DONE_INT_ENA1", + 16, + 1 + ], + [ + "SLC0_RX_EOF_INT_ENA1", + 17, + 1 + ], + [ + "SLC0_TOHOST_INT_ENA1", + 18, + 1 + ], + [ + "SLC0_TX_DSCR_ERR_INT_ENA1", + 19, + 1 + ], + [ + "SLC0_RX_DSCR_ERR_INT_ENA1", + 20, + 1 + ], + [ + "SLC0_TX_DSCR_EMPTY_INT_ENA1", + 21, + 1 + ], + [ + "SLC0_HOST_RD_ACK_INT_ENA1", + 22, + 1 + ], + [ + "SLC0_WR_RETRY_DONE_INT_ENA1", + 23, + 1 + ], + [ + "SLC0_TX_ERR_EOF_INT_ENA1", + 24, + 1 + ], + [ + "CMD_DTC_INT_ENA1", + 25, + 1 + ], + [ + "SLC0_RX_QUICK_EOF_INT_ENA1", + 26, + 1 + ] + ] + }, + { + "name": "_1INT_ST1", + "offset": "0x144", + "fields": [ + [ + "FRHOST_BIT8_INT_ST1", + 0, + 1, + "r" + ], + [ + "FRHOST_BIT9_INT_ST1", + 1, + 1, + "r" + ], + [ + "FRHOST_BIT10_INT_ST1", + 2, + 1, + "r" + ], + [ + "FRHOST_BIT11_INT_ST1", + 3, + 1, + "r" + ], + [ + "FRHOST_BIT12_INT_ST1", + 4, + 1, + "r" + ], + [ + "FRHOST_BIT13_INT_ST1", + 5, + 1, + "r" + ], + [ + "FRHOST_BIT14_INT_ST1", + 6, + 1, + "r" + ], + [ + "FRHOST_BIT15_INT_ST1", + 7, + 1, + "r" + ], + [ + "SLC1_RX_START_INT_ST1", + 8, + 1, + "r" + ], + [ + "SLC1_TX_START_INT_ST1", + 9, + 1, + "r" + ], + [ + "SLC1_RX_UDF_INT_ST1", + 10, + 1, + "r" + ], + [ + "SLC1_TX_OVF_INT_ST1", + 11, + 1, + "r" + ], + [ + "SLC1_TOKEN0_1TO0_INT_ST1", + 12, + 1, + "r" + ], + [ + "SLC1_TOKEN1_1TO0_INT_ST1", + 13, + 1, + "r" + ], + [ + "SLC1_TX_DONE_INT_ST1", + 14, + 1, + "r" + ], + [ + "SLC1_TX_SUC_EOF_INT_ST1", + 15, + 1, + "r" + ], + [ + "SLC1_RX_DONE_INT_ST1", + 16, + 1, + "r" + ], + [ + "SLC1_RX_EOF_INT_ST1", + 17, + 1, + "r" + ], + [ + "SLC1_TOHOST_INT_ST1", + 18, + 1, + "r" + ], + [ + "SLC1_TX_DSCR_ERR_INT_ST1", + 19, + 1, + "r" + ], + [ + "SLC1_RX_DSCR_ERR_INT_ST1", + 20, + 1, + "r" + ], + [ + "SLC1_TX_DSCR_EMPTY_INT_ST1", + 21, + 1, + "r" + ], + [ + "SLC1_HOST_RD_ACK_INT_ST1", + 22, + 1, + "r" + ], + [ + "SLC1_WR_RETRY_DONE_INT_ST1", + 23, + 1, + "r" + ], + [ + "SLC1_TX_ERR_EOF_INT_ST1", + 24, + 1, + "r" + ] + ] + }, + { + "name": "_1INT_ENA1", + "offset": "0x148", + "fields": [ + [ + "FRHOST_BIT8_INT_ENA1", + 0, + 1 + ], + [ + "FRHOST_BIT9_INT_ENA1", + 1, + 1 + ], + [ + "FRHOST_BIT10_INT_ENA1", + 2, + 1 + ], + [ + "FRHOST_BIT11_INT_ENA1", + 3, + 1 + ], + [ + "FRHOST_BIT12_INT_ENA1", + 4, + 1 + ], + [ + "FRHOST_BIT13_INT_ENA1", + 5, + 1 + ], + [ + "FRHOST_BIT14_INT_ENA1", + 6, + 1 + ], + [ + "FRHOST_BIT15_INT_ENA1", + 7, + 1 + ], + [ + "SLC1_RX_START_INT_ENA1", + 8, + 1 + ], + [ + "SLC1_TX_START_INT_ENA1", + 9, + 1 + ], + [ + "SLC1_RX_UDF_INT_ENA1", + 10, + 1 + ], + [ + "SLC1_TX_OVF_INT_ENA1", + 11, + 1 + ], + [ + "SLC1_TOKEN0_1TO0_INT_ENA1", + 12, + 1 + ], + [ + "SLC1_TOKEN1_1TO0_INT_ENA1", + 13, + 1 + ], + [ + "SLC1_TX_DONE_INT_ENA1", + 14, + 1 + ], + [ + "SLC1_TX_SUC_EOF_INT_ENA1", + 15, + 1 + ], + [ + "SLC1_RX_DONE_INT_ENA1", + 16, + 1 + ], + [ + "SLC1_RX_EOF_INT_ENA1", + 17, + 1 + ], + [ + "SLC1_TOHOST_INT_ENA1", + 18, + 1 + ], + [ + "SLC1_TX_DSCR_ERR_INT_ENA1", + 19, + 1 + ], + [ + "SLC1_RX_DSCR_ERR_INT_ENA1", + 20, + 1 + ], + [ + "SLC1_TX_DSCR_EMPTY_INT_ENA1", + 21, + 1 + ], + [ + "SLC1_HOST_RD_ACK_INT_ENA1", + 22, + 1 + ], + [ + "SLC1_WR_RETRY_DONE_INT_ENA1", + 23, + 1 + ], + [ + "SLC1_TX_ERR_EOF_INT_ENA1", + 24, + 1 + ] + ] + }, + { + "name": "DATE", + "offset": "0x1F8", + "reset": "0x16022500", + "fields": [ + [ + "DATE", + 0, + 32 + ] + ] + }, + { + "name": "ID", + "offset": "0x1FC", + "reset": "0x00000100", + "fields": [ + [ + "ID", + 0, + 32 + ] + ] + } + ] + }, + { + "name": "LEDC", + "base": "0x3FF59000", + "interrupts": [ + "LEDC", + "TIMER1", + "TIMER2" + ], + "registers": [ + { + "name": "HSCH0_CONF0", + "offset": "0x00", + "fields": [ + [ + "TIMER_SEL", + 0, + 2 + ], + [ + "SIG_OUT_EN", + 2, + 1 + ], + [ + "IDLE_LV", + 3, + 1 + ] + ] + }, + { + "name": "HSCH1_CONF0", + "offset": "0x14", + "fields": [ + [ + "TIMER_SEL", + 0, + 2 + ], + [ + "SIG_OUT_EN", + 2, + 1 + ], + [ + "IDLE_LV", + 3, + 1 + ] + ] + }, + { + "name": "HSCH2_CONF0", + "offset": "0x28", + "fields": [ + [ + "TIMER_SEL", + 0, + 2 + ], + [ + "SIG_OUT_EN", + 2, + 1 + ], + [ + "IDLE_LV", + 3, + 1 + ] + ] + }, + { + "name": "HSCH3_CONF0", + "offset": "0x3C", + "fields": [ + [ + "TIMER_SEL", + 0, + 2 + ], + [ + "SIG_OUT_EN", + 2, + 1 + ], + [ + "IDLE_LV", + 3, + 1 + ] + ] + }, + { + "name": "HSCH4_CONF0", + "offset": "0x50", + "fields": [ + [ + "TIMER_SEL", + 0, + 2 + ], + [ + "SIG_OUT_EN", + 2, + 1 + ], + [ + "IDLE_LV", + 3, + 1 + ] + ] + }, + { + "name": "HSCH5_CONF0", + "offset": "0x64", + "fields": [ + [ + "TIMER_SEL", + 0, + 2 + ], + [ + "SIG_OUT_EN", + 2, + 1 + ], + [ + "IDLE_LV", + 3, + 1 + ] + ] + }, + { + "name": "HSCH6_CONF0", + "offset": "0x78", + "fields": [ + [ + "TIMER_SEL", + 0, + 2 + ], + [ + "SIG_OUT_EN", + 2, + 1 + ], + [ + "IDLE_LV", + 3, + 1 + ] + ] + }, + { + "name": "HSCH7_CONF0", + "offset": "0x8C", + "fields": [ + [ + "TIMER_SEL", + 0, + 2 + ], + [ + "SIG_OUT_EN", + 2, + 1 + ], + [ + "IDLE_LV", + 3, + 1 + ] + ] + }, + { + "name": "HSCH0_HPOINT", + "offset": "0x04", + "fields": [ + [ + "HPOINT", + 0, + 20 + ] + ] + }, + { + "name": "HSCH1_HPOINT", + "offset": "0x18", + "fields": [ + [ + "HPOINT", + 0, + 20 + ] + ] + }, + { + "name": "HSCH2_HPOINT", + "offset": "0x2C", + "fields": [ + [ + "HPOINT", + 0, + 20 + ] + ] + }, + { + "name": "HSCH3_HPOINT", + "offset": "0x40", + "fields": [ + [ + "HPOINT", + 0, + 20 + ] + ] + }, + { + "name": "HSCH4_HPOINT", + "offset": "0x54", + "fields": [ + [ + "HPOINT", + 0, + 20 + ] + ] + }, + { + "name": "HSCH5_HPOINT", + "offset": "0x68", + "fields": [ + [ + "HPOINT", + 0, + 20 + ] + ] + }, + { + "name": "HSCH6_HPOINT", + "offset": "0x7C", + "fields": [ + [ + "HPOINT", + 0, + 20 + ] + ] + }, + { + "name": "HSCH7_HPOINT", + "offset": "0x90", + "fields": [ + [ + "HPOINT", + 0, + 20 + ] + ] + }, + { + "name": "HSCH0_DUTY", + "offset": "0x08", + "fields": [ + [ + "DUTY", + 0, + 25 + ] + ] + }, + { + "name": "HSCH1_DUTY", + "offset": "0x1C", + "fields": [ + [ + "DUTY", + 0, + 25 + ] + ] + }, + { + "name": "HSCH2_DUTY", + "offset": "0x30", + "fields": [ + [ + "DUTY", + 0, + 25 + ] + ] + }, + { + "name": "HSCH3_DUTY", + "offset": "0x44", + "fields": [ + [ + "DUTY", + 0, + 25 + ] + ] + }, + { + "name": "HSCH4_DUTY", + "offset": "0x58", + "fields": [ + [ + "DUTY", + 0, + 25 + ] + ] + }, + { + "name": "HSCH5_DUTY", + "offset": "0x6C", + "fields": [ + [ + "DUTY", + 0, + 25 + ] + ] + }, + { + "name": "HSCH6_DUTY", + "offset": "0x80", + "fields": [ + [ + "DUTY", + 0, + 25 + ] + ] + }, + { + "name": "HSCH7_DUTY", + "offset": "0x94", + "fields": [ + [ + "DUTY", + 0, + 25 + ] + ] + }, + { + "name": "HSCH0_CONF1", + "offset": "0x0C", + "reset": "0x40000000", + "fields": [ + [ + "DUTY_SCALE", + 0, + 10 + ], + [ + "DUTY_CYCLE", + 10, + 10 + ], + [ + "DUTY_NUM", + 20, + 10 + ], + [ + "DUTY_INC", + 30, + 1 + ], + [ + "DUTY_START", + 31, + 1 + ] + ] + }, + { + "name": "HSCH1_CONF1", + "offset": "0x20", + "reset": "0x40000000", + "fields": [ + [ + "DUTY_SCALE", + 0, + 10 + ], + [ + "DUTY_CYCLE", + 10, + 10 + ], + [ + "DUTY_NUM", + 20, + 10 + ], + [ + "DUTY_INC", + 30, + 1 + ], + [ + "DUTY_START", + 31, + 1 + ] + ] + }, + { + "name": "HSCH2_CONF1", + "offset": "0x34", + "reset": "0x40000000", + "fields": [ + [ + "DUTY_SCALE", + 0, + 10 + ], + [ + "DUTY_CYCLE", + 10, + 10 + ], + [ + "DUTY_NUM", + 20, + 10 + ], + [ + "DUTY_INC", + 30, + 1 + ], + [ + "DUTY_START", + 31, + 1 + ] + ] + }, + { + "name": "HSCH3_CONF1", + "offset": "0x48", + "reset": "0x40000000", + "fields": [ + [ + "DUTY_SCALE", + 0, + 10 + ], + [ + "DUTY_CYCLE", + 10, + 10 + ], + [ + "DUTY_NUM", + 20, + 10 + ], + [ + "DUTY_INC", + 30, + 1 + ], + [ + "DUTY_START", + 31, + 1 + ] + ] + }, + { + "name": "HSCH4_CONF1", + "offset": "0x5C", + "reset": "0x40000000", + "fields": [ + [ + "DUTY_SCALE", + 0, + 10 + ], + [ + "DUTY_CYCLE", + 10, + 10 + ], + [ + "DUTY_NUM", + 20, + 10 + ], + [ + "DUTY_INC", + 30, + 1 + ], + [ + "DUTY_START", + 31, + 1 + ] + ] + }, + { + "name": "HSCH5_CONF1", + "offset": "0x70", + "reset": "0x40000000", + "fields": [ + [ + "DUTY_SCALE", + 0, + 10 + ], + [ + "DUTY_CYCLE", + 10, + 10 + ], + [ + "DUTY_NUM", + 20, + 10 + ], + [ + "DUTY_INC", + 30, + 1 + ], + [ + "DUTY_START", + 31, + 1 + ] + ] + }, + { + "name": "HSCH6_CONF1", + "offset": "0x84", + "reset": "0x40000000", + "fields": [ + [ + "DUTY_SCALE", + 0, + 10 + ], + [ + "DUTY_CYCLE", + 10, + 10 + ], + [ + "DUTY_NUM", + 20, + 10 + ], + [ + "DUTY_INC", + 30, + 1 + ], + [ + "DUTY_START", + 31, + 1 + ] + ] + }, + { + "name": "HSCH7_CONF1", + "offset": "0x98", + "reset": "0x40000000", + "fields": [ + [ + "DUTY_SCALE", + 0, + 10 + ], + [ + "DUTY_CYCLE", + 10, + 10 + ], + [ + "DUTY_NUM", + 20, + 10 + ], + [ + "DUTY_INC", + 30, + 1 + ], + [ + "DUTY_START", + 31, + 1 + ] + ] + }, + { + "name": "HSCH0_DUTY_R", + "offset": "0x10", + "fields": [ + [ + "DUTY_R", + 0, + 25, + "r" + ] + ] + }, + { + "name": "HSCH1_DUTY_R", + "offset": "0x24", + "fields": [ + [ + "DUTY_R", + 0, + 25, + "r" + ] + ] + }, + { + "name": "HSCH2_DUTY_R", + "offset": "0x38", + "fields": [ + [ + "DUTY_R", + 0, + 25, + "r" + ] + ] + }, + { + "name": "HSCH3_DUTY_R", + "offset": "0x4C", + "fields": [ + [ + "DUTY_R", + 0, + 25, + "r" + ] + ] + }, + { + "name": "HSCH4_DUTY_R", + "offset": "0x60", + "fields": [ + [ + "DUTY_R", + 0, + 25, + "r" + ] + ] + }, + { + "name": "HSCH5_DUTY_R", + "offset": "0x74", + "fields": [ + [ + "DUTY_R", + 0, + 25, + "r" + ] + ] + }, + { + "name": "HSCH6_DUTY_R", + "offset": "0x88", + "fields": [ + [ + "DUTY_R", + 0, + 25, + "r" + ] + ] + }, + { + "name": "HSCH7_DUTY_R", + "offset": "0x9C", + "fields": [ + [ + "DUTY_R", + 0, + 25, + "r" + ] + ] + }, + { + "name": "LSCH0_CONF0", + "offset": "0xA0", + "fields": [ + [ + "TIMER_SEL", + 0, + 2 + ], + [ + "SIG_OUT_EN", + 2, + 1 + ], + [ + "IDLE_LV", + 3, + 1 + ], + [ + "PARA_UP", + 4, + 1 + ] + ] + }, + { + "name": "LSCH1_CONF0", + "offset": "0xB4", + "fields": [ + [ + "TIMER_SEL", + 0, + 2 + ], + [ + "SIG_OUT_EN", + 2, + 1 + ], + [ + "IDLE_LV", + 3, + 1 + ], + [ + "PARA_UP", + 4, + 1 + ] + ] + }, + { + "name": "LSCH2_CONF0", + "offset": "0xC8", + "fields": [ + [ + "TIMER_SEL", + 0, + 2 + ], + [ + "SIG_OUT_EN", + 2, + 1 + ], + [ + "IDLE_LV", + 3, + 1 + ], + [ + "PARA_UP", + 4, + 1 + ] + ] + }, + { + "name": "LSCH3_CONF0", + "offset": "0xDC", + "fields": [ + [ + "TIMER_SEL", + 0, + 2 + ], + [ + "SIG_OUT_EN", + 2, + 1 + ], + [ + "IDLE_LV", + 3, + 1 + ], + [ + "PARA_UP", + 4, + 1 + ] + ] + }, + { + "name": "LSCH4_CONF0", + "offset": "0xF0", + "fields": [ + [ + "TIMER_SEL", + 0, + 2 + ], + [ + "SIG_OUT_EN", + 2, + 1 + ], + [ + "IDLE_LV", + 3, + 1 + ], + [ + "PARA_UP", + 4, + 1 + ] + ] + }, + { + "name": "LSCH5_CONF0", + "offset": "0x104", + "fields": [ + [ + "TIMER_SEL", + 0, + 2 + ], + [ + "SIG_OUT_EN", + 2, + 1 + ], + [ + "IDLE_LV", + 3, + 1 + ], + [ + "PARA_UP", + 4, + 1 + ] + ] + }, + { + "name": "LSCH6_CONF0", + "offset": "0x118", + "fields": [ + [ + "TIMER_SEL", + 0, + 2 + ], + [ + "SIG_OUT_EN", + 2, + 1 + ], + [ + "IDLE_LV", + 3, + 1 + ], + [ + "PARA_UP", + 4, + 1 + ] + ] + }, + { + "name": "LSCH7_CONF0", + "offset": "0x12C", + "fields": [ + [ + "TIMER_SEL", + 0, + 2 + ], + [ + "SIG_OUT_EN", + 2, + 1 + ], + [ + "IDLE_LV", + 3, + 1 + ], + [ + "PARA_UP", + 4, + 1 + ] + ] + }, + { + "name": "LSCH0_HPOINT", + "offset": "0xA4", + "fields": [ + [ + "HPOINT", + 0, + 20 + ] + ] + }, + { + "name": "LSCH1_HPOINT", + "offset": "0xB8", + "fields": [ + [ + "HPOINT", + 0, + 20 + ] + ] + }, + { + "name": "LSCH2_HPOINT", + "offset": "0xCC", + "fields": [ + [ + "HPOINT", + 0, + 20 + ] + ] + }, + { + "name": "LSCH3_HPOINT", + "offset": "0xE0", + "fields": [ + [ + "HPOINT", + 0, + 20 + ] + ] + }, + { + "name": "LSCH4_HPOINT", + "offset": "0xF4", + "fields": [ + [ + "HPOINT", + 0, + 20 + ] + ] + }, + { + "name": "LSCH5_HPOINT", + "offset": "0x108", + "fields": [ + [ + "HPOINT", + 0, + 20 + ] + ] + }, + { + "name": "LSCH6_HPOINT", + "offset": "0x11C", + "fields": [ + [ + "HPOINT", + 0, + 20 + ] + ] + }, + { + "name": "LSCH7_HPOINT", + "offset": "0x130", + "fields": [ + [ + "HPOINT", + 0, + 20 + ] + ] + }, + { + "name": "LSCH0_DUTY", + "offset": "0xA8", + "fields": [ + [ + "DUTY", + 0, + 25 + ] + ] + }, + { + "name": "LSCH1_DUTY", + "offset": "0xBC", + "fields": [ + [ + "DUTY", + 0, + 25 + ] + ] + }, + { + "name": "LSCH2_DUTY", + "offset": "0xD0", + "fields": [ + [ + "DUTY", + 0, + 25 + ] + ] + }, + { + "name": "LSCH3_DUTY", + "offset": "0xE4", + "fields": [ + [ + "DUTY", + 0, + 25 + ] + ] + }, + { + "name": "LSCH4_DUTY", + "offset": "0xF8", + "fields": [ + [ + "DUTY", + 0, + 25 + ] + ] + }, + { + "name": "LSCH5_DUTY", + "offset": "0x10C", + "fields": [ + [ + "DUTY", + 0, + 25 + ] + ] + }, + { + "name": "LSCH6_DUTY", + "offset": "0x120", + "fields": [ + [ + "DUTY", + 0, + 25 + ] + ] + }, + { + "name": "LSCH7_DUTY", + "offset": "0x134", + "fields": [ + [ + "DUTY", + 0, + 25 + ] + ] + }, + { + "name": "LSCH0_CONF1", + "offset": "0xAC", + "reset": "0x40000000", + "fields": [ + [ + "DUTY_SCALE", + 0, + 10 + ], + [ + "DUTY_CYCLE", + 10, + 10 + ], + [ + "DUTY_NUM", + 20, + 10 + ], + [ + "DUTY_INC", + 30, + 1 + ], + [ + "DUTY_START", + 31, + 1 + ] + ] + }, + { + "name": "LSCH1_CONF1", + "offset": "0xC0", + "reset": "0x40000000", + "fields": [ + [ + "DUTY_SCALE", + 0, + 10 + ], + [ + "DUTY_CYCLE", + 10, + 10 + ], + [ + "DUTY_NUM", + 20, + 10 + ], + [ + "DUTY_INC", + 30, + 1 + ], + [ + "DUTY_START", + 31, + 1 + ] + ] + }, + { + "name": "LSCH2_CONF1", + "offset": "0xD4", + "reset": "0x40000000", + "fields": [ + [ + "DUTY_SCALE", + 0, + 10 + ], + [ + "DUTY_CYCLE", + 10, + 10 + ], + [ + "DUTY_NUM", + 20, + 10 + ], + [ + "DUTY_INC", + 30, + 1 + ], + [ + "DUTY_START", + 31, + 1 + ] + ] + }, + { + "name": "LSCH3_CONF1", + "offset": "0xE8", + "reset": "0x40000000", + "fields": [ + [ + "DUTY_SCALE", + 0, + 10 + ], + [ + "DUTY_CYCLE", + 10, + 10 + ], + [ + "DUTY_NUM", + 20, + 10 + ], + [ + "DUTY_INC", + 30, + 1 + ], + [ + "DUTY_START", + 31, + 1 + ] + ] + }, + { + "name": "LSCH4_CONF1", + "offset": "0xFC", + "reset": "0x40000000", + "fields": [ + [ + "DUTY_SCALE", + 0, + 10 + ], + [ + "DUTY_CYCLE", + 10, + 10 + ], + [ + "DUTY_NUM", + 20, + 10 + ], + [ + "DUTY_INC", + 30, + 1 + ], + [ + "DUTY_START", + 31, + 1 + ] + ] + }, + { + "name": "LSCH5_CONF1", + "offset": "0x110", + "reset": "0x40000000", + "fields": [ + [ + "DUTY_SCALE", + 0, + 10 + ], + [ + "DUTY_CYCLE", + 10, + 10 + ], + [ + "DUTY_NUM", + 20, + 10 + ], + [ + "DUTY_INC", + 30, + 1 + ], + [ + "DUTY_START", + 31, + 1 + ] + ] + }, + { + "name": "LSCH6_CONF1", + "offset": "0x124", + "reset": "0x40000000", + "fields": [ + [ + "DUTY_SCALE", + 0, + 10 + ], + [ + "DUTY_CYCLE", + 10, + 10 + ], + [ + "DUTY_NUM", + 20, + 10 + ], + [ + "DUTY_INC", + 30, + 1 + ], + [ + "DUTY_START", + 31, + 1 + ] + ] + }, + { + "name": "LSCH7_CONF1", + "offset": "0x138", + "reset": "0x40000000", + "fields": [ + [ + "DUTY_SCALE", + 0, + 10 + ], + [ + "DUTY_CYCLE", + 10, + 10 + ], + [ + "DUTY_NUM", + 20, + 10 + ], + [ + "DUTY_INC", + 30, + 1 + ], + [ + "DUTY_START", + 31, + 1 + ] + ] + }, + { + "name": "LSCH0_DUTY_R", + "offset": "0xB0", + "fields": [ + [ + "DUTY_R", + 0, + 25, + "r" + ] + ] + }, + { + "name": "LSCH1_DUTY_R", + "offset": "0xC4", + "fields": [ + [ + "DUTY_R", + 0, + 25, + "r" + ] + ] + }, + { + "name": "LSCH2_DUTY_R", + "offset": "0xD8", + "fields": [ + [ + "DUTY_R", + 0, + 25, + "r" + ] + ] + }, + { + "name": "LSCH3_DUTY_R", + "offset": "0xEC", + "fields": [ + [ + "DUTY_R", + 0, + 25, + "r" + ] + ] + }, + { + "name": "LSCH4_DUTY_R", + "offset": "0x100", + "fields": [ + [ + "DUTY_R", + 0, + 25, + "r" + ] + ] + }, + { + "name": "LSCH5_DUTY_R", + "offset": "0x114", + "fields": [ + [ + "DUTY_R", + 0, + 25, + "r" + ] + ] + }, + { + "name": "LSCH6_DUTY_R", + "offset": "0x128", + "fields": [ + [ + "DUTY_R", + 0, + 25, + "r" + ] + ] + }, + { + "name": "LSCH7_DUTY_R", + "offset": "0x13C", + "fields": [ + [ + "DUTY_R", + 0, + 25, + "r" + ] + ] + }, + { + "name": "HSTIMER0_CONF", + "offset": "0x140", + "reset": "0x01000000", + "fields": [ + [ + "DUTY_RES", + 0, + 5 + ], + [ + "DIV_NUM", + 5, + 18 + ], + [ + "PAUSE", + 23, + 1 + ], + [ + "RST", + 24, + 1 + ], + [ + "TICK_SEL", + 25, + 1 + ] + ] + }, + { + "name": "HSTIMER1_CONF", + "offset": "0x148", + "reset": "0x01000000", + "fields": [ + [ + "DUTY_RES", + 0, + 5 + ], + [ + "DIV_NUM", + 5, + 18 + ], + [ + "PAUSE", + 23, + 1 + ], + [ + "RST", + 24, + 1 + ], + [ + "TICK_SEL", + 25, + 1 + ] + ] + }, + { + "name": "HSTIMER2_CONF", + "offset": "0x150", + "reset": "0x01000000", + "fields": [ + [ + "DUTY_RES", + 0, + 5 + ], + [ + "DIV_NUM", + 5, + 18 + ], + [ + "PAUSE", + 23, + 1 + ], + [ + "RST", + 24, + 1 + ], + [ + "TICK_SEL", + 25, + 1 + ] + ] + }, + { + "name": "HSTIMER3_CONF", + "offset": "0x158", + "reset": "0x01000000", + "fields": [ + [ + "DUTY_RES", + 0, + 5 + ], + [ + "DIV_NUM", + 5, + 18 + ], + [ + "PAUSE", + 23, + 1 + ], + [ + "RST", + 24, + 1 + ], + [ + "TICK_SEL", + 25, + 1 + ] + ] + }, + { + "name": "HSTIMER0_VALUE", + "offset": "0x144", + "fields": [ + [ + "CNT", + 0, + 20, + "r" + ] + ] + }, + { + "name": "HSTIMER1_VALUE", + "offset": "0x14C", + "fields": [ + [ + "CNT", + 0, + 20, + "r" + ] + ] + }, + { + "name": "HSTIMER2_VALUE", + "offset": "0x154", + "fields": [ + [ + "CNT", + 0, + 20, + "r" + ] + ] + }, + { + "name": "HSTIMER3_VALUE", + "offset": "0x15C", + "fields": [ + [ + "CNT", + 0, + 20, + "r" + ] + ] + }, + { + "name": "LSTIMER0_CONF", + "offset": "0x160", + "reset": "0x01000000", + "fields": [ + [ + "DUTY_RES", + 0, + 5 + ], + [ + "DIV_NUM", + 5, + 18 + ], + [ + "PAUSE", + 23, + 1 + ], + [ + "RST", + 24, + 1 + ], + [ + "TICK_SEL", + 25, + 1 + ], + [ + "PARA_UP", + 26, + 1 + ] + ] + }, + { + "name": "LSTIMER1_CONF", + "offset": "0x168", + "reset": "0x01000000", + "fields": [ + [ + "DUTY_RES", + 0, + 5 + ], + [ + "DIV_NUM", + 5, + 18 + ], + [ + "PAUSE", + 23, + 1 + ], + [ + "RST", + 24, + 1 + ], + [ + "TICK_SEL", + 25, + 1 + ], + [ + "PARA_UP", + 26, + 1 + ] + ] + }, + { + "name": "LSTIMER2_CONF", + "offset": "0x170", + "reset": "0x01000000", + "fields": [ + [ + "DUTY_RES", + 0, + 5 + ], + [ + "DIV_NUM", + 5, + 18 + ], + [ + "PAUSE", + 23, + 1 + ], + [ + "RST", + 24, + 1 + ], + [ + "TICK_SEL", + 25, + 1 + ], + [ + "PARA_UP", + 26, + 1 + ] + ] + }, + { + "name": "LSTIMER3_CONF", + "offset": "0x178", + "reset": "0x01000000", + "fields": [ + [ + "DUTY_RES", + 0, + 5 + ], + [ + "DIV_NUM", + 5, + 18 + ], + [ + "PAUSE", + 23, + 1 + ], + [ + "RST", + 24, + 1 + ], + [ + "TICK_SEL", + 25, + 1 + ], + [ + "PARA_UP", + 26, + 1 + ] + ] + }, + { + "name": "LSTIMER0_VALUE", + "offset": "0x164", + "fields": [ + [ + "CNT", + 0, + 20, + "r" + ] + ] + }, + { + "name": "LSTIMER1_VALUE", + "offset": "0x16C", + "fields": [ + [ + "CNT", + 0, + 20, + "r" + ] + ] + }, + { + "name": "LSTIMER2_VALUE", + "offset": "0x174", + "fields": [ + [ + "CNT", + 0, + 20, + "r" + ] + ] + }, + { + "name": "LSTIMER3_VALUE", + "offset": "0x17C", + "fields": [ + [ + "CNT", + 0, + 20, + "r" + ] + ] + }, + { + "name": "INT_RAW", + "offset": "0x180", + "fields": [ + [ + "HSTIMER0_OVF_INT_RAW", + 0, + 1, + "r" + ], + [ + "HSTIMER1_OVF_INT_RAW", + 1, + 1, + "r" + ], + [ + "HSTIMER2_OVF_INT_RAW", + 2, + 1, + "r" + ], + [ + "HSTIMER3_OVF_INT_RAW", + 3, + 1, + "r" + ], + [ + "LSTIMER0_OVF_INT_RAW", + 4, + 1, + "r" + ], + [ + "LSTIMER1_OVF_INT_RAW", + 5, + 1, + "r" + ], + [ + "LSTIMER2_OVF_INT_RAW", + 6, + 1, + "r" + ], + [ + "LSTIMER3_OVF_INT_RAW", + 7, + 1, + "r" + ], + [ + "DUTY_CHNG_END_HSCH0_INT_RAW", + 8, + 1, + "r" + ], + [ + "DUTY_CHNG_END_HSCH1_INT_RAW", + 9, + 1, + "r" + ], + [ + "DUTY_CHNG_END_HSCH2_INT_RAW", + 10, + 1, + "r" + ], + [ + "DUTY_CHNG_END_HSCH3_INT_RAW", + 11, + 1, + "r" + ], + [ + "DUTY_CHNG_END_HSCH4_INT_RAW", + 12, + 1, + "r" + ], + [ + "DUTY_CHNG_END_HSCH5_INT_RAW", + 13, + 1, + "r" + ], + [ + "DUTY_CHNG_END_HSCH6_INT_RAW", + 14, + 1, + "r" + ], + [ + "DUTY_CHNG_END_HSCH7_INT_RAW", + 15, + 1, + "r" + ], + [ + "DUTY_CHNG_END_LSCH0_INT_RAW", + 16, + 1, + "r" + ], + [ + "DUTY_CHNG_END_LSCH1_INT_RAW", + 17, + 1, + "r" + ], + [ + "DUTY_CHNG_END_LSCH2_INT_RAW", + 18, + 1, + "r" + ], + [ + "DUTY_CHNG_END_LSCH3_INT_RAW", + 19, + 1, + "r" + ], + [ + "DUTY_CHNG_END_LSCH4_INT_RAW", + 20, + 1, + "r" + ], + [ + "DUTY_CHNG_END_LSCH5_INT_RAW", + 21, + 1, + "r" + ], + [ + "DUTY_CHNG_END_LSCH6_INT_RAW", + 22, + 1, + "r" + ], + [ + "DUTY_CHNG_END_LSCH7_INT_RAW", + 23, + 1, + "r" + ] + ] + }, + { + "name": "INT_ST", + "offset": "0x184", + "fields": [ + [ + "HSTIMER0_OVF_INT_ST", + 0, + 1, + "r" + ], + [ + "HSTIMER1_OVF_INT_ST", + 1, + 1, + "r" + ], + [ + "HSTIMER2_OVF_INT_ST", + 2, + 1, + "r" + ], + [ + "HSTIMER3_OVF_INT_ST", + 3, + 1, + "r" + ], + [ + "LSTIMER0_OVF_INT_ST", + 4, + 1, + "r" + ], + [ + "LSTIMER1_OVF_INT_ST", + 5, + 1, + "r" + ], + [ + "LSTIMER2_OVF_INT_ST", + 6, + 1, + "r" + ], + [ + "LSTIMER3_OVF_INT_ST", + 7, + 1, + "r" + ], + [ + "DUTY_CHNG_END_HSCH0_INT_ST", + 8, + 1, + "r" + ], + [ + "DUTY_CHNG_END_HSCH1_INT_ST", + 9, + 1, + "r" + ], + [ + "DUTY_CHNG_END_HSCH2_INT_ST", + 10, + 1, + "r" + ], + [ + "DUTY_CHNG_END_HSCH3_INT_ST", + 11, + 1, + "r" + ], + [ + "DUTY_CHNG_END_HSCH4_INT_ST", + 12, + 1, + "r" + ], + [ + "DUTY_CHNG_END_HSCH5_INT_ST", + 13, + 1, + "r" + ], + [ + "DUTY_CHNG_END_HSCH6_INT_ST", + 14, + 1, + "r" + ], + [ + "DUTY_CHNG_END_HSCH7_INT_ST", + 15, + 1, + "r" + ], + [ + "DUTY_CHNG_END_LSCH0_INT_ST", + 16, + 1, + "r" + ], + [ + "DUTY_CHNG_END_LSCH1_INT_ST", + 17, + 1, + "r" + ], + [ + "DUTY_CHNG_END_LSCH2_INT_ST", + 18, + 1, + "r" + ], + [ + "DUTY_CHNG_END_LSCH3_INT_ST", + 19, + 1, + "r" + ], + [ + "DUTY_CHNG_END_LSCH4_INT_ST", + 20, + 1, + "r" + ], + [ + "DUTY_CHNG_END_LSCH5_INT_ST", + 21, + 1, + "r" + ], + [ + "DUTY_CHNG_END_LSCH6_INT_ST", + 22, + 1, + "r" + ], + [ + "DUTY_CHNG_END_LSCH7_INT_ST", + 23, + 1, + "r" + ] + ] + }, + { + "name": "INT_ENA", + "offset": "0x188", + "fields": [ + [ + "HSTIMER0_OVF_INT_ENA", + 0, + 1 + ], + [ + "HSTIMER1_OVF_INT_ENA", + 1, + 1 + ], + [ + "HSTIMER2_OVF_INT_ENA", + 2, + 1 + ], + [ + "HSTIMER3_OVF_INT_ENA", + 3, + 1 + ], + [ + "LSTIMER0_OVF_INT_ENA", + 4, + 1 + ], + [ + "LSTIMER1_OVF_INT_ENA", + 5, + 1 + ], + [ + "LSTIMER2_OVF_INT_ENA", + 6, + 1 + ], + [ + "LSTIMER3_OVF_INT_ENA", + 7, + 1 + ], + [ + "DUTY_CHNG_END_HSCH0_INT_ENA", + 8, + 1 + ], + [ + "DUTY_CHNG_END_HSCH1_INT_ENA", + 9, + 1 + ], + [ + "DUTY_CHNG_END_HSCH2_INT_ENA", + 10, + 1 + ], + [ + "DUTY_CHNG_END_HSCH3_INT_ENA", + 11, + 1 + ], + [ + "DUTY_CHNG_END_HSCH4_INT_ENA", + 12, + 1 + ], + [ + "DUTY_CHNG_END_HSCH5_INT_ENA", + 13, + 1 + ], + [ + "DUTY_CHNG_END_HSCH6_INT_ENA", + 14, + 1 + ], + [ + "DUTY_CHNG_END_HSCH7_INT_ENA", + 15, + 1 + ], + [ + "DUTY_CHNG_END_LSCH0_INT_ENA", + 16, + 1 + ], + [ + "DUTY_CHNG_END_LSCH1_INT_ENA", + 17, + 1 + ], + [ + "DUTY_CHNG_END_LSCH2_INT_ENA", + 18, + 1 + ], + [ + "DUTY_CHNG_END_LSCH3_INT_ENA", + 19, + 1 + ], + [ + "DUTY_CHNG_END_LSCH4_INT_ENA", + 20, + 1 + ], + [ + "DUTY_CHNG_END_LSCH5_INT_ENA", + 21, + 1 + ], + [ + "DUTY_CHNG_END_LSCH6_INT_ENA", + 22, + 1 + ], + [ + "DUTY_CHNG_END_LSCH7_INT_ENA", + 23, + 1 + ] + ] + }, + { + "name": "INT_CLR", + "offset": "0x18C", + "fields": [ + [ + "HSTIMER0_OVF_INT_CLR", + 0, + 1, + "w" + ], + [ + "HSTIMER1_OVF_INT_CLR", + 1, + 1, + "w" + ], + [ + "HSTIMER2_OVF_INT_CLR", + 2, + 1, + "w" + ], + [ + "HSTIMER3_OVF_INT_CLR", + 3, + 1, + "w" + ], + [ + "LSTIMER0_OVF_INT_CLR", + 4, + 1, + "w" + ], + [ + "LSTIMER1_OVF_INT_CLR", + 5, + 1, + "w" + ], + [ + "LSTIMER2_OVF_INT_CLR", + 6, + 1, + "w" + ], + [ + "LSTIMER3_OVF_INT_CLR", + 7, + 1, + "w" + ], + [ + "DUTY_CHNG_END_HSCH0_INT_CLR", + 8, + 1, + "w" + ], + [ + "DUTY_CHNG_END_HSCH1_INT_CLR", + 9, + 1, + "w" + ], + [ + "DUTY_CHNG_END_HSCH2_INT_CLR", + 10, + 1, + "w" + ], + [ + "DUTY_CHNG_END_HSCH3_INT_CLR", + 11, + 1, + "w" + ], + [ + "DUTY_CHNG_END_HSCH4_INT_CLR", + 12, + 1, + "w" + ], + [ + "DUTY_CHNG_END_HSCH5_INT_CLR", + 13, + 1, + "w" + ], + [ + "DUTY_CHNG_END_HSCH6_INT_CLR", + 14, + 1, + "w" + ], + [ + "DUTY_CHNG_END_HSCH7_INT_CLR", + 15, + 1, + "w" + ], + [ + "DUTY_CHNG_END_LSCH0_INT_CLR", + 16, + 1, + "w" + ], + [ + "DUTY_CHNG_END_LSCH1_INT_CLR", + 17, + 1, + "w" + ], + [ + "DUTY_CHNG_END_LSCH2_INT_CLR", + 18, + 1, + "w" + ], + [ + "DUTY_CHNG_END_LSCH3_INT_CLR", + 19, + 1, + "w" + ], + [ + "DUTY_CHNG_END_LSCH4_INT_CLR", + 20, + 1, + "w" + ], + [ + "DUTY_CHNG_END_LSCH5_INT_CLR", + 21, + 1, + "w" + ], + [ + "DUTY_CHNG_END_LSCH6_INT_CLR", + 22, + 1, + "w" + ], + [ + "DUTY_CHNG_END_LSCH7_INT_CLR", + 23, + 1, + "w" + ] + ] + }, + { + "name": "CONF", + "offset": "0x190", + "fields": [ + [ + "APB_CLK_SEL", + 0, + 1 + ] + ] + }, + { + "name": "DATE", + "offset": "0x1FC", + "reset": "0x16031700", + "fields": [ + [ + "DATE", + 0, + 32 + ] + ] + } + ] + }, + { + "name": "EFUSE", + "base": "0x3FF5A000", + "interrupts": [ + "EFUSE" + ], + "registers": [ + { + "name": "BLK0_RDATA0", + "offset": "0x00", + "fields": [ + [ + "RD_EFUSE_WR_DIS", + 0, + 16, + "r" + ], + [ + "RD_EFUSE_RD_DIS", + 16, + 4, + "r" + ], + [ + "RD_FLASH_CRYPT_CNT", + 20, + 7, + "r" + ], + [ + "RD_UART_DOWNLOAD_DIS", + 27, + 1, + "r" + ], + [ + "RESERVED_0_28", + 28, + 4, + "r" + ] + ] + }, + { + "name": "BLK0_RDATA1", + "offset": "0x04", + "fields": [ + [ + "RD_MAC", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK0_RDATA2", + "offset": "0x08", + "fields": [ + [ + "RD_MAC_1", + 0, + 16, + "r" + ], + [ + "RD_MAC_CRC", + 16, + 8, + "r" + ], + [ + "RD_RESERVE_0_88", + 24, + 8 + ] + ] + }, + { + "name": "BLK0_RDATA3", + "offset": "0x0C", + "fields": [ + [ + "RD_DISABLE_APP_CPU", + 0, + 1, + "r" + ], + [ + "RD_DISABLE_BT", + 1, + 1, + "r" + ], + [ + "RD_CHIP_PACKAGE_4BIT", + 2, + 1, + "r" + ], + [ + "RD_DIS_CACHE", + 3, + 1, + "r" + ], + [ + "RD_SPI_PAD_CONFIG_HD", + 4, + 5, + "r" + ], + [ + "RD_CHIP_PACKAGE", + 9, + 3 + ], + [ + "RD_CHIP_CPU_FREQ_LOW", + 12, + 1 + ], + [ + "RD_CHIP_CPU_FREQ_RATED", + 13, + 1 + ], + [ + "RD_BLK3_PART_RESERVE", + 14, + 1 + ], + [ + "RD_CHIP_VER_REV1", + 15, + 1 + ], + [ + "RD_RESERVE_0_112", + 16, + 16 + ] + ] + }, + { + "name": "BLK0_RDATA4", + "offset": "0x10", + "fields": [ + [ + "RD_CLK8M_FREQ", + 0, + 8, + "r" + ], + [ + "RD_ADC_VREF", + 8, + 5 + ], + [ + "RD_RESERVE_0_141", + 13, + 1 + ], + [ + "RD_XPD_SDIO", + 14, + 1, + "r" + ], + [ + "RD_XPD_SDIO_TIEH", + 15, + 1, + "r" + ], + [ + "RD_XPD_SDIO_FORCE", + 16, + 1, + "r" + ], + [ + "RD_RESERVE_0_145", + 17, + 15 + ] + ] + }, + { + "name": "BLK0_RDATA5", + "offset": "0x14", + "fields": [ + [ + "RD_SPI_PAD_CONFIG_CLK", + 0, + 5, + "r" + ], + [ + "RD_SPI_PAD_CONFIG_Q", + 5, + 5, + "r" + ], + [ + "RD_SPI_PAD_CONFIG_D", + 10, + 5, + "r" + ], + [ + "RD_SPI_PAD_CONFIG_CS0", + 15, + 5, + "r" + ], + [ + "RD_CHIP_VER_REV2", + 20, + 1, + "r" + ], + [ + "RD_RESERVE_0_181", + 21, + 1 + ], + [ + "RD_VOL_LEVEL_HP_INV", + 22, + 2, + "r" + ], + [ + "RD_WAFER_VERSION_MINOR", + 24, + 2, + "r" + ], + [ + "RD_RESERVE_0_186", + 26, + 2 + ], + [ + "RD_FLASH_CRYPT_CONFIG", + 28, + 4, + "r" + ] + ] + }, + { + "name": "BLK0_RDATA6", + "offset": "0x18", + "fields": [ + [ + "RD_CODING_SCHEME", + 0, + 2, + "r" + ], + [ + "RD_CONSOLE_DEBUG_DISABLE", + 2, + 1, + "r" + ], + [ + "RD_DISABLE_SDIO_HOST", + 3, + 1, + "r" + ], + [ + "RD_ABS_DONE_0", + 4, + 1, + "r" + ], + [ + "RD_ABS_DONE_1", + 5, + 1, + "r" + ], + [ + "RD_JTAG_DISABLE", + 6, + 1, + "r" + ], + [ + "RD_DISABLE_DL_ENCRYPT", + 7, + 1, + "r" + ], + [ + "RD_DISABLE_DL_DECRYPT", + 8, + 1, + "r" + ], + [ + "RD_DISABLE_DL_CACHE", + 9, + 1, + "r" + ], + [ + "RD_KEY_STATUS", + 10, + 1, + "r" + ], + [ + "RD_RESERVE_0_203", + 11, + 21 + ] + ] + }, + { + "name": "BLK0_WDATA0", + "offset": "0x1C", + "fields": [ + [ + "WR_DIS", + 0, + 16 + ], + [ + "RD_DIS", + 16, + 4 + ], + [ + "FLASH_CRYPT_CNT", + 20, + 7 + ] + ] + }, + { + "name": "BLK0_WDATA1", + "offset": "0x20", + "fields": [ + [ + "WIFI_MAC_CRC_LOW", + 0, + 32 + ] + ] + }, + { + "name": "BLK0_WDATA2", + "offset": "0x24", + "fields": [ + [ + "WIFI_MAC_CRC_HIGH", + 0, + 24 + ] + ] + }, + { + "name": "BLK0_WDATA3", + "offset": "0x28", + "fields": [ + [ + "DISABLE_APP_CPU", + 0, + 1, + "r" + ], + [ + "DISABLE_BT", + 1, + 1, + "r" + ], + [ + "CHIP_PACKAGE_4BIT", + 2, + 1, + "r" + ], + [ + "DIS_CACHE", + 3, + 1, + "r" + ], + [ + "SPI_PAD_CONFIG_HD", + 4, + 5, + "r" + ], + [ + "CHIP_PACKAGE", + 9, + 3 + ], + [ + "CHIP_CPU_FREQ_LOW", + 12, + 1 + ], + [ + "CHIP_CPU_FREQ_RATED", + 13, + 1 + ], + [ + "BLK3_PART_RESERVE", + 14, + 1 + ], + [ + "CHIP_VER_REV1", + 15, + 1 + ], + [ + "RESERVE_0_112", + 16, + 16 + ] + ] + }, + { + "name": "BLK0_WDATA4", + "offset": "0x2C", + "fields": [ + [ + "CLK8M_FREQ", + 0, + 8, + "r" + ], + [ + "ADC_VREF", + 8, + 5 + ], + [ + "RESERVE_0_141", + 13, + 1 + ], + [ + "XPD_SDIO", + 14, + 1, + "r" + ], + [ + "XPD_SDIO_TIEH", + 15, + 1, + "r" + ], + [ + "XPD_SDIO_FORCE", + 16, + 1, + "r" + ], + [ + "RESERVE_0_145", + 17, + 15 + ] + ] + }, + { + "name": "BLK0_WDATA5", + "offset": "0x30", + "fields": [ + [ + "SPI_PAD_CONFIG_CLK", + 0, + 5, + "r" + ], + [ + "SPI_PAD_CONFIG_Q", + 5, + 5, + "r" + ], + [ + "SPI_PAD_CONFIG_D", + 10, + 5, + "r" + ], + [ + "SPI_PAD_CONFIG_CS0", + 15, + 5, + "r" + ], + [ + "CHIP_VER_REV2", + 20, + 1, + "r" + ], + [ + "RESERVE_0_181", + 21, + 1 + ], + [ + "VOL_LEVEL_HP_INV", + 22, + 2, + "r" + ], + [ + "WAFER_VERSION_MINOR", + 24, + 2, + "r" + ], + [ + "RESERVE_0_186", + 26, + 2 + ], + [ + "FLASH_CRYPT_CONFIG", + 28, + 4, + "r" + ] + ] + }, + { + "name": "BLK0_WDATA6", + "offset": "0x34", + "fields": [ + [ + "CODING_SCHEME", + 0, + 2 + ], + [ + "CONSOLE_DEBUG_DISABLE", + 2, + 1 + ], + [ + "DISABLE_SDIO_HOST", + 3, + 1 + ], + [ + "ABS_DONE_0", + 4, + 1 + ], + [ + "ABS_DONE_1", + 5, + 1 + ], + [ + "DISABLE_JTAG", + 6, + 1 + ], + [ + "DISABLE_DL_ENCRYPT", + 7, + 1 + ], + [ + "DISABLE_DL_DECRYPT", + 8, + 1 + ], + [ + "DISABLE_DL_CACHE", + 9, + 1 + ], + [ + "KEY_STATUS", + 10, + 1 + ] + ] + }, + { + "name": "BLK1_RDATA0", + "offset": "0x38", + "fields": [ + [ + "RD_BLOCK1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK1_RDATA1", + "offset": "0x3C", + "fields": [ + [ + "RD_BLOCK1_1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK1_RDATA2", + "offset": "0x40", + "fields": [ + [ + "RD_BLOCK1_2", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK1_RDATA3", + "offset": "0x44", + "fields": [ + [ + "RD_BLOCK1_3", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK1_RDATA4", + "offset": "0x48", + "fields": [ + [ + "RD_BLOCK1_4", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK1_RDATA5", + "offset": "0x4C", + "fields": [ + [ + "RD_BLOCK1_5", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK1_RDATA6", + "offset": "0x50", + "fields": [ + [ + "RD_BLOCK1_6", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK1_RDATA7", + "offset": "0x54", + "fields": [ + [ + "RD_BLOCK1_7", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK2_RDATA0", + "offset": "0x58", + "fields": [ + [ + "RD_BLOCK2", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK2_RDATA1", + "offset": "0x5C", + "fields": [ + [ + "RD_BLOCK2_1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK2_RDATA2", + "offset": "0x60", + "fields": [ + [ + "RD_BLOCK2_2", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK2_RDATA3", + "offset": "0x64", + "fields": [ + [ + "RD_BLOCK2_3", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK2_RDATA4", + "offset": "0x68", + "fields": [ + [ + "RD_BLOCK2_4", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK2_RDATA5", + "offset": "0x6C", + "fields": [ + [ + "RD_BLOCK2_5", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK2_RDATA6", + "offset": "0x70", + "fields": [ + [ + "RD_BLOCK2_6", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK2_RDATA7", + "offset": "0x74", + "fields": [ + [ + "RD_BLOCK2_7", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK3_RDATA0", + "offset": "0x78", + "fields": [ + [ + "RD_CUSTOM_MAC_CRC", + 0, + 8, + "r" + ], + [ + "RD_CUSTOM_MAC", + 8, + 24, + "r" + ] + ] + }, + { + "name": "BLK3_RDATA1", + "offset": "0x7C", + "fields": [ + [ + "RD_CUSTOM_MAC_1", + 0, + 24, + "r" + ], + [ + "RESERVED_3_56", + 24, + 8, + "r" + ] + ] + }, + { + "name": "BLK3_RDATA2", + "offset": "0x80", + "fields": [ + [ + "RD_BLK3_RESERVED_2", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK3_RDATA3", + "offset": "0x84", + "fields": [ + [ + "RD_ADC1_TP_LOW", + 0, + 7 + ], + [ + "RD_ADC1_TP_HIGH", + 7, + 9 + ], + [ + "RD_ADC2_TP_LOW", + 16, + 7 + ], + [ + "RD_ADC2_TP_HIGH", + 23, + 9 + ] + ] + }, + { + "name": "BLK3_RDATA4", + "offset": "0x88", + "fields": [ + [ + "RD_SECURE_VERSION", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK3_RDATA5", + "offset": "0x8C", + "fields": [ + [ + "RESERVED_3_160", + 0, + 24, + "r" + ], + [ + "RD_MAC_VERSION", + 24, + 8, + "r" + ] + ] + }, + { + "name": "BLK3_RDATA6", + "offset": "0x90", + "fields": [ + [ + "RD_BLK3_RESERVED_6", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK3_RDATA7", + "offset": "0x94", + "fields": [ + [ + "RD_BLK3_RESERVED_7", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK1_WDATA0", + "offset": "0x98", + "fields": [ + [ + "BLK1_DIN0", + 0, + 32 + ] + ] + }, + { + "name": "BLK1_WDATA1", + "offset": "0x9C", + "fields": [ + [ + "BLK1_DIN1", + 0, + 32 + ] + ] + }, + { + "name": "BLK1_WDATA2", + "offset": "0xA0", + "fields": [ + [ + "BLK1_DIN2", + 0, + 32 + ] + ] + }, + { + "name": "BLK1_WDATA3", + "offset": "0xA4", + "fields": [ + [ + "BLK1_DIN3", + 0, + 32 + ] + ] + }, + { + "name": "BLK1_WDATA4", + "offset": "0xA8", + "fields": [ + [ + "BLK1_DIN4", + 0, + 32 + ] + ] + }, + { + "name": "BLK1_WDATA5", + "offset": "0xAC", + "fields": [ + [ + "BLK1_DIN5", + 0, + 32 + ] + ] + }, + { + "name": "BLK1_WDATA6", + "offset": "0xB0", + "fields": [ + [ + "BLK1_DIN6", + 0, + 32 + ] + ] + }, + { + "name": "BLK1_WDATA7", + "offset": "0xB4", + "fields": [ + [ + "BLK1_DIN7", + 0, + 32 + ] + ] + }, + { + "name": "BLK2_WDATA0", + "offset": "0xB8", + "fields": [ + [ + "BLK2_DIN0", + 0, + 32 + ] + ] + }, + { + "name": "BLK2_WDATA1", + "offset": "0xBC", + "fields": [ + [ + "BLK2_DIN1", + 0, + 32 + ] + ] + }, + { + "name": "BLK2_WDATA2", + "offset": "0xC0", + "fields": [ + [ + "BLK2_DIN2", + 0, + 32 + ] + ] + }, + { + "name": "BLK2_WDATA3", + "offset": "0xC4", + "fields": [ + [ + "BLK2_DIN3", + 0, + 32 + ] + ] + }, + { + "name": "BLK2_WDATA4", + "offset": "0xC8", + "fields": [ + [ + "BLK2_DIN4", + 0, + 32 + ] + ] + }, + { + "name": "BLK2_WDATA5", + "offset": "0xCC", + "fields": [ + [ + "BLK2_DIN5", + 0, + 32 + ] + ] + }, + { + "name": "BLK2_WDATA6", + "offset": "0xD0", + "fields": [ + [ + "BLK2_DIN6", + 0, + 32 + ] + ] + }, + { + "name": "BLK2_WDATA7", + "offset": "0xD4", + "fields": [ + [ + "BLK2_DIN7", + 0, + 32 + ] + ] + }, + { + "name": "BLK3_WDATA0", + "offset": "0xD8", + "fields": [ + [ + "BLK3_DIN0", + 0, + 32 + ] + ] + }, + { + "name": "BLK3_WDATA1", + "offset": "0xDC", + "fields": [ + [ + "BLK3_DIN1", + 0, + 32 + ] + ] + }, + { + "name": "BLK3_WDATA2", + "offset": "0xE0", + "fields": [ + [ + "BLK3_DIN2", + 0, + 32 + ] + ] + }, + { + "name": "BLK3_WDATA3", + "offset": "0xE4", + "fields": [ + [ + "ADC1_TP_LOW", + 0, + 7 + ], + [ + "ADC1_TP_HIGH", + 7, + 9 + ], + [ + "ADC2_TP_LOW", + 16, + 7 + ], + [ + "ADC2_TP_HIGH", + 23, + 9 + ] + ] + }, + { + "name": "BLK3_WDATA4", + "offset": "0xE8", + "fields": [ + [ + "SECURE_VERSION", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BLK3_WDATA5", + "offset": "0xEC", + "fields": [ + [ + "BLK3_DIN5", + 0, + 32 + ] + ] + }, + { + "name": "BLK3_WDATA6", + "offset": "0xF0", + "fields": [ + [ + "BLK3_DIN6", + 0, + 32 + ] + ] + }, + { + "name": "BLK3_WDATA7", + "offset": "0xF4", + "fields": [ + [ + "BLK3_DIN7", + 0, + 32 + ] + ] + }, + { + "name": "CLK", + "offset": "0xF8", + "reset": "0x00004052", + "fields": [ + [ + "SEL0", + 0, + 8 + ], + [ + "SEL1", + 8, + 8 + ], + [ + "EN", + 16, + 1 + ] + ] + }, + { + "name": "CONF", + "offset": "0xFC", + "reset": "0x00010000", + "fields": [ + [ + "OP_CODE", + 0, + 16 + ], + [ + "FORCE_NO_WR_RD_DIS", + 16, + 1 + ] + ] + }, + { + "name": "STATUS", + "offset": "0x100", + "fields": [ + [ + "DEBUG", + 0, + 32, + "r" + ] + ] + }, + { + "name": "CMD", + "offset": "0x104", + "fields": [ + [ + "READ_CMD", + 0, + 1 + ], + [ + "PGM_CMD", + 1, + 1 + ] + ] + }, + { + "name": "INT_RAW", + "offset": "0x108", + "fields": [ + [ + "READ_DONE_INT_RAW", + 0, + 1, + "r" + ], + [ + "PGM_DONE_INT_RAW", + 1, + 1, + "r" + ] + ] + }, + { + "name": "INT_ST", + "offset": "0x10C", + "fields": [ + [ + "READ_DONE_INT_ST", + 0, + 1, + "r" + ], + [ + "PGM_DONE_INT_ST", + 1, + 1, + "r" + ] + ] + }, + { + "name": "INT_ENA", + "offset": "0x110", + "fields": [ + [ + "READ_DONE_INT_ENA", + 0, + 1 + ], + [ + "PGM_DONE_INT_ENA", + 1, + 1 + ] + ] + }, + { + "name": "INT_CLR", + "offset": "0x114", + "fields": [ + [ + "READ_DONE_INT_CLR", + 0, + 1, + "w" + ], + [ + "PGM_DONE_INT_CLR", + 1, + 1, + "w" + ] + ] + }, + { + "name": "DAC_CONF", + "offset": "0x118", + "reset": "0x00000028", + "fields": [ + [ + "DAC_CLK_DIV", + 0, + 8 + ], + [ + "DAC_CLK_PAD_SEL", + 8, + 1 + ] + ] + }, + { + "name": "DEC_STATUS", + "offset": "0x11C", + "fields": [ + [ + "DEC_WARNINGS", + 0, + 12, + "r" + ] + ] + }, + { + "name": "DATE", + "offset": "0x1FC", + "reset": "0x16042600", + "fields": [ + [ + "DATE", + 0, + 32 + ] + ] + } + ] + }, + { + "name": "NRX", + "base": "0x3FF5CC00", + "registers": [ + { + "name": "NRXPD_CTRL", + "offset": "0xD4", + "fields": [ + [ + "DEMAP_FORCE_PD", + 0, + 1 + ], + [ + "DEMAP_FORCE_PU", + 1, + 1 + ], + [ + "VIT_FORCE_PD", + 2, + 1 + ], + [ + "VIT_FORCE_PU", + 3, + 1 + ], + [ + "RX_ROT_FORCE_PD", + 4, + 1 + ], + [ + "RX_ROT_FORCE_PU", + 5, + 1 + ], + [ + "CHAN_EST_FORCE_PD", + 6, + 1 + ], + [ + "CHAN_EST_FORCE_PU", + 7, + 1 + ] + ] + } + ] + }, + { + "name": "BB", + "base": "0x3FF5D000", + "registers": [ + { + "name": "BBPD_CTRL", + "offset": "0x54", + "fields": [ + [ + "DC_EST_FORCE_PD", + 0, + 1 + ], + [ + "DC_EST_FORCE_PU", + 1, + 1 + ], + [ + "FFT_FORCE_PD", + 2, + 1 + ], + [ + "FFT_FORCE_PU", + 3, + 1 + ] + ] + } + ] + }, + { + "name": "MCPWM0", + "base": "0x3FF5E000", + "interrupts": [ + "MCPWM0", + "MCPWM1", + "MCPWM2", + "MCPWM3" + ], + "registers": [ + { + "name": "CLK_CFG", + "offset": "0x00", + "fields": [ + [ + "CLK_PRESCALE", + 0, + 8 + ] + ] + }, + { + "name": "TIMER0_CFG0", + "offset": "0x04", + "reset": "0x0000FF00", + "fields": [ + [ + "TIMER0_PRESCALE", + 0, + 8 + ], + [ + "TIMER0_PERIOD", + 8, + 16 + ], + [ + "TIMER0_PERIOD_UPMETHOD", + 24, + 2 + ] + ] + }, + { + "name": "TIMER0_CFG1", + "offset": "0x08", + "fields": [ + [ + "TIMER0_START", + 0, + 3 + ], + [ + "TIMER0_MOD", + 3, + 2 + ] + ] + }, + { + "name": "TIMER0_SYNC", + "offset": "0x0C", + "fields": [ + [ + "TIMER0_SYNCI_EN", + 0, + 1 + ], + [ + "SW", + 1, + 1 + ], + [ + "TIMER0_SYNCO_SEL", + 2, + 2 + ], + [ + "TIMER0_PHASE", + 4, + 16 + ], + [ + "TIMER0_PHASE_DIRECTION", + 20, + 1 + ] + ] + }, + { + "name": "TIMER0_STATUS", + "offset": "0x10", + "fields": [ + [ + "TIMER0_VALUE", + 0, + 16, + "r" + ], + [ + "TIMER0_DIRECTION", + 16, + 1, + "r" + ] + ] + }, + { + "name": "TIMER1_CFG0", + "offset": "0x14", + "reset": "0x0000FF00", + "fields": [ + [ + "TIMER1_PRESCALE", + 0, + 8 + ], + [ + "TIMER1_PERIOD", + 8, + 16 + ], + [ + "TIMER1_PERIOD_UPMETHOD", + 24, + 2 + ] + ] + }, + { + "name": "TIMER1_CFG1", + "offset": "0x18", + "fields": [ + [ + "TIMER1_START", + 0, + 3 + ], + [ + "TIMER1_MOD", + 3, + 2 + ] + ] + }, + { + "name": "TIMER1_SYNC", + "offset": "0x1C", + "fields": [ + [ + "TIMER1_SYNCI_EN", + 0, + 1 + ], + [ + "SW", + 1, + 1 + ], + [ + "TIMER1_SYNCO_SEL", + 2, + 2 + ], + [ + "TIMER1_PHASE", + 4, + 16 + ], + [ + "TIMER1_PHASE_DIRECTION", + 20, + 1 + ] + ] + }, + { + "name": "TIMER1_STATUS", + "offset": "0x20", + "fields": [ + [ + "TIMER1_VALUE", + 0, + 16, + "r" + ], + [ + "TIMER1_DIRECTION", + 16, + 1, + "r" + ] + ] + }, + { + "name": "TIMER2_CFG0", + "offset": "0x24", + "reset": "0x0000FF00", + "fields": [ + [ + "TIMER2_PRESCALE", + 0, + 8 + ], + [ + "TIMER2_PERIOD", + 8, + 16 + ], + [ + "TIMER2_PERIOD_UPMETHOD", + 24, + 2 + ] + ] + }, + { + "name": "TIMER2_CFG1", + "offset": "0x28", + "fields": [ + [ + "TIMER2_START", + 0, + 3 + ], + [ + "TIMER2_MOD", + 3, + 2 + ] + ] + }, + { + "name": "TIMER2_SYNC", + "offset": "0x2C", + "fields": [ + [ + "TIMER2_SYNCI_EN", + 0, + 1 + ], + [ + "SW", + 1, + 1 + ], + [ + "TIMER2_SYNCO_SEL", + 2, + 2 + ], + [ + "TIMER2_PHASE", + 4, + 16 + ], + [ + "TIMER2_PHASE_DIRECTION", + 20, + 1 + ] + ] + }, + { + "name": "TIMER2_STATUS", + "offset": "0x30", + "fields": [ + [ + "TIMER2_VALUE", + 0, + 16, + "r" + ], + [ + "TIMER2_DIRECTION", + 16, + 1, + "r" + ] + ] + }, + { + "name": "TIMER_SYNCI_CFG", + "offset": "0x34", + "fields": [ + [ + "TIMER0_SYNCISEL", + 0, + 3 + ], + [ + "TIMER1_SYNCISEL", + 3, + 3 + ], + [ + "TIMER2_SYNCISEL", + 6, + 3 + ], + [ + "EXTERNAL_SYNCI0_INVERT", + 9, + 1 + ], + [ + "EXTERNAL_SYNCI1_INVERT", + 10, + 1 + ], + [ + "EXTERNAL_SYNCI2_INVERT", + 11, + 1 + ] + ] + }, + { + "name": "OPERATOR_TIMERSEL", + "offset": "0x38", + "fields": [ + [ + "OPERATOR0_TIMERSEL", + 0, + 2 + ], + [ + "OPERATOR1_TIMERSEL", + 2, + 2 + ], + [ + "OPERATOR2_TIMERSEL", + 4, + 2 + ] + ] + }, + { + "name": "GEN0_STMP_CFG", + "offset": "0x3C", + "fields": [ + [ + "GEN0_A_UPMETHOD", + 0, + 4 + ], + [ + "GEN0_B_UPMETHOD", + 4, + 4 + ], + [ + "GEN0_A_SHDW_FULL", + 8, + 1 + ], + [ + "GEN0_B_SHDW_FULL", + 9, + 1 + ] + ] + }, + { + "name": "GEN0_TSTMP_A", + "offset": "0x40", + "fields": [ + [ + "GEN0_A", + 0, + 16 + ] + ] + }, + { + "name": "GEN0_TSTMP_B", + "offset": "0x44", + "fields": [ + [ + "GEN0_B", + 0, + 16 + ] + ] + }, + { + "name": "GEN0_CFG0", + "offset": "0x48", + "fields": [ + [ + "GEN0_CFG_UPMETHOD", + 0, + 4 + ], + [ + "GEN0_T0_SEL", + 4, + 3 + ], + [ + "GEN0_T1_SEL", + 7, + 3 + ] + ] + }, + { + "name": "GEN0_FORCE", + "offset": "0x4C", + "reset": "0x00000020", + "fields": [ + [ + "GEN0_CNTUFORCE_UPMETHOD", + 0, + 6 + ], + [ + "GEN0_A_CNTUFORCE_MODE", + 6, + 2 + ], + [ + "GEN0_B_CNTUFORCE_MODE", + 8, + 2 + ], + [ + "GEN0_A_NCIFORCE", + 10, + 1 + ], + [ + "GEN0_A_NCIFORCE_MODE", + 11, + 2 + ], + [ + "GEN0_B_NCIFORCE", + 13, + 1 + ], + [ + "GEN0_B_NCIFORCE_MODE", + 14, + 2 + ] + ] + }, + { + "name": "GEN0_A", + "offset": "0x50", + "fields": [ + [ + "UTEZ", + 0, + 2 + ], + [ + "UTEP", + 2, + 2 + ], + [ + "UTEA", + 4, + 2 + ], + [ + "UTEB", + 6, + 2 + ], + [ + "UT0", + 8, + 2 + ], + [ + "UT1", + 10, + 2 + ], + [ + "DTEZ", + 12, + 2 + ], + [ + "DTEP", + 14, + 2 + ], + [ + "DTEA", + 16, + 2 + ], + [ + "DTEB", + 18, + 2 + ], + [ + "DT0", + 20, + 2 + ], + [ + "DT1", + 22, + 2 + ] + ] + }, + { + "name": "GEN0_B", + "offset": "0x54", + "fields": [ + [ + "UTEZ", + 0, + 2 + ], + [ + "UTEP", + 2, + 2 + ], + [ + "UTEA", + 4, + 2 + ], + [ + "UTEB", + 6, + 2 + ], + [ + "UT0", + 8, + 2 + ], + [ + "UT1", + 10, + 2 + ], + [ + "DTEZ", + 12, + 2 + ], + [ + "DTEP", + 14, + 2 + ], + [ + "DTEA", + 16, + 2 + ], + [ + "DTEB", + 18, + 2 + ], + [ + "DT0", + 20, + 2 + ], + [ + "DT1", + 22, + 2 + ] + ] + }, + { + "name": "DT0_CFG", + "offset": "0x58", + "reset": "0x00018000", + "fields": [ + [ + "DT0_FED_UPMETHOD", + 0, + 4 + ], + [ + "DT0_RED_UPMETHOD", + 4, + 4 + ], + [ + "DT0_DEB_MODE", + 8, + 1 + ], + [ + "DT0_A_OUTSWAP", + 9, + 1 + ], + [ + "DT0_B_OUTSWAP", + 10, + 1 + ], + [ + "DT0_RED_INSEL", + 11, + 1 + ], + [ + "DT0_FED_INSEL", + 12, + 1 + ], + [ + "DT0_RED_OUTINVERT", + 13, + 1 + ], + [ + "DT0_FED_OUTINVERT", + 14, + 1 + ], + [ + "DT0_A_OUTBYPASS", + 15, + 1 + ], + [ + "DT0_B_OUTBYPASS", + 16, + 1 + ], + [ + "DT0_CLK_SEL", + 17, + 1 + ] + ] + }, + { + "name": "DT0_FED_CFG", + "offset": "0x5C", + "fields": [ + [ + "DT0_FED", + 0, + 16 + ] + ] + }, + { + "name": "DT0_RED_CFG", + "offset": "0x60", + "fields": [ + [ + "DT0_RED", + 0, + 16 + ] + ] + }, + { + "name": "CARRIER0_CFG", + "offset": "0x64", + "fields": [ + [ + "CARRIER0_EN", + 0, + 1 + ], + [ + "CARRIER0_PRESCALE", + 1, + 4 + ], + [ + "CARRIER0_DUTY", + 5, + 3 + ], + [ + "CARRIER0_OSHTWTH", + 8, + 4 + ], + [ + "CARRIER0_OUT_INVERT", + 12, + 1 + ], + [ + "CARRIER0_IN_INVERT", + 13, + 1 + ] + ] + }, + { + "name": "FH0_CFG0", + "offset": "0x68", + "fields": [ + [ + "FH0_SW_CBC", + 0, + 1 + ], + [ + "FH0_F2_CBC", + 1, + 1 + ], + [ + "FH0_F1_CBC", + 2, + 1 + ], + [ + "FH0_F0_CBC", + 3, + 1 + ], + [ + "FH0_SW_OST", + 4, + 1 + ], + [ + "FH0_F2_OST", + 5, + 1 + ], + [ + "FH0_F1_OST", + 6, + 1 + ], + [ + "FH0_F0_OST", + 7, + 1 + ], + [ + "FH0_A_CBC_D", + 8, + 2 + ], + [ + "FH0_A_CBC_U", + 10, + 2 + ], + [ + "FH0_A_OST_D", + 12, + 2 + ], + [ + "FH0_A_OST_U", + 14, + 2 + ], + [ + "FH0_B_CBC_D", + 16, + 2 + ], + [ + "FH0_B_CBC_U", + 18, + 2 + ], + [ + "FH0_B_OST_D", + 20, + 2 + ], + [ + "FH0_B_OST_U", + 22, + 2 + ] + ] + }, + { + "name": "FH0_CFG1", + "offset": "0x6C", + "fields": [ + [ + "FH0_CLR_OST", + 0, + 1 + ], + [ + "FH0_CBCPULSE", + 1, + 2 + ], + [ + "FH0_FORCE_CBC", + 3, + 1 + ], + [ + "FH0_FORCE_OST", + 4, + 1 + ] + ] + }, + { + "name": "FH0_STATUS", + "offset": "0x70", + "fields": [ + [ + "FH0_CBC_ON", + 0, + 1, + "r" + ], + [ + "FH0_OST_ON", + 1, + 1, + "r" + ] + ] + }, + { + "name": "GEN1_STMP_CFG", + "offset": "0x74", + "fields": [ + [ + "GEN1_A_UPMETHOD", + 0, + 4 + ], + [ + "GEN1_B_UPMETHOD", + 4, + 4 + ], + [ + "GEN1_A_SHDW_FULL", + 8, + 1 + ], + [ + "GEN1_B_SHDW_FULL", + 9, + 1 + ] + ] + }, + { + "name": "GEN1_TSTMP_A", + "offset": "0x78", + "fields": [ + [ + "GEN1_A", + 0, + 16 + ] + ] + }, + { + "name": "GEN1_TSTMP_B", + "offset": "0x7C", + "fields": [ + [ + "GEN1_B", + 0, + 16 + ] + ] + }, + { + "name": "GEN1_CFG0", + "offset": "0x80", + "fields": [ + [ + "GEN1_CFG_UPMETHOD", + 0, + 4 + ], + [ + "GEN1_T0_SEL", + 4, + 3 + ], + [ + "GEN1_T1_SEL", + 7, + 3 + ] + ] + }, + { + "name": "GEN1_FORCE", + "offset": "0x84", + "reset": "0x00000020", + "fields": [ + [ + "GEN1_CNTUFORCE_UPMETHOD", + 0, + 6 + ], + [ + "GEN1_A_CNTUFORCE_MODE", + 6, + 2 + ], + [ + "GEN1_B_CNTUFORCE_MODE", + 8, + 2 + ], + [ + "GEN1_A_NCIFORCE", + 10, + 1 + ], + [ + "GEN1_A_NCIFORCE_MODE", + 11, + 2 + ], + [ + "GEN1_B_NCIFORCE", + 13, + 1 + ], + [ + "GEN1_B_NCIFORCE_MODE", + 14, + 2 + ] + ] + }, + { + "name": "GEN1_A", + "offset": "0x88", + "fields": [ + [ + "UTEZ", + 0, + 2 + ], + [ + "UTEP", + 2, + 2 + ], + [ + "UTEA", + 4, + 2 + ], + [ + "UTEB", + 6, + 2 + ], + [ + "UT0", + 8, + 2 + ], + [ + "UT1", + 10, + 2 + ], + [ + "DTEZ", + 12, + 2 + ], + [ + "DTEP", + 14, + 2 + ], + [ + "DTEA", + 16, + 2 + ], + [ + "DTEB", + 18, + 2 + ], + [ + "DT0", + 20, + 2 + ], + [ + "DT1", + 22, + 2 + ] + ] + }, + { + "name": "GEN1_B", + "offset": "0x8C", + "fields": [ + [ + "UTEZ", + 0, + 2 + ], + [ + "UTEP", + 2, + 2 + ], + [ + "UTEA", + 4, + 2 + ], + [ + "UTEB", + 6, + 2 + ], + [ + "UT0", + 8, + 2 + ], + [ + "UT1", + 10, + 2 + ], + [ + "DTEZ", + 12, + 2 + ], + [ + "DTEP", + 14, + 2 + ], + [ + "DTEA", + 16, + 2 + ], + [ + "DTEB", + 18, + 2 + ], + [ + "DT0", + 20, + 2 + ], + [ + "DT1", + 22, + 2 + ] + ] + }, + { + "name": "DT1_CFG", + "offset": "0x90", + "reset": "0x00018000", + "fields": [ + [ + "DT1_FED_UPMETHOD", + 0, + 4 + ], + [ + "DT1_RED_UPMETHOD", + 4, + 4 + ], + [ + "DT1_DEB_MODE", + 8, + 1 + ], + [ + "DT1_A_OUTSWAP", + 9, + 1 + ], + [ + "DT1_B_OUTSWAP", + 10, + 1 + ], + [ + "DT1_RED_INSEL", + 11, + 1 + ], + [ + "DT1_FED_INSEL", + 12, + 1 + ], + [ + "DT1_RED_OUTINVERT", + 13, + 1 + ], + [ + "DT1_FED_OUTINVERT", + 14, + 1 + ], + [ + "DT1_A_OUTBYPASS", + 15, + 1 + ], + [ + "DT1_B_OUTBYPASS", + 16, + 1 + ], + [ + "DT1_CLK_SEL", + 17, + 1 + ] + ] + }, + { + "name": "DT1_FED_CFG", + "offset": "0x94", + "fields": [ + [ + "DT1_FED", + 0, + 16 + ] + ] + }, + { + "name": "DT1_RED_CFG", + "offset": "0x98", + "fields": [ + [ + "DT1_RED", + 0, + 16 + ] + ] + }, + { + "name": "CARRIER1_CFG", + "offset": "0x9C", + "fields": [ + [ + "CARRIER1_EN", + 0, + 1 + ], + [ + "CARRIER1_PRESCALE", + 1, + 4 + ], + [ + "CARRIER1_DUTY", + 5, + 3 + ], + [ + "CARRIER1_OSHTWTH", + 8, + 4 + ], + [ + "CARRIER1_OUT_INVERT", + 12, + 1 + ], + [ + "CARRIER1_IN_INVERT", + 13, + 1 + ] + ] + }, + { + "name": "FH1_CFG0", + "offset": "0xA0", + "fields": [ + [ + "FH1_SW_CBC", + 0, + 1 + ], + [ + "FH1_F2_CBC", + 1, + 1 + ], + [ + "FH1_F1_CBC", + 2, + 1 + ], + [ + "FH1_F0_CBC", + 3, + 1 + ], + [ + "FH1_SW_OST", + 4, + 1 + ], + [ + "FH1_F2_OST", + 5, + 1 + ], + [ + "FH1_F1_OST", + 6, + 1 + ], + [ + "FH1_F0_OST", + 7, + 1 + ], + [ + "FH1_A_CBC_D", + 8, + 2 + ], + [ + "FH1_A_CBC_U", + 10, + 2 + ], + [ + "FH1_A_OST_D", + 12, + 2 + ], + [ + "FH1_A_OST_U", + 14, + 2 + ], + [ + "FH1_B_CBC_D", + 16, + 2 + ], + [ + "FH1_B_CBC_U", + 18, + 2 + ], + [ + "FH1_B_OST_D", + 20, + 2 + ], + [ + "FH1_B_OST_U", + 22, + 2 + ] + ] + }, + { + "name": "FH1_CFG1", + "offset": "0xA4", + "fields": [ + [ + "FH1_CLR_OST", + 0, + 1 + ], + [ + "FH1_CBCPULSE", + 1, + 2 + ], + [ + "FH1_FORCE_CBC", + 3, + 1 + ], + [ + "FH1_FORCE_OST", + 4, + 1 + ] + ] + }, + { + "name": "FH1_STATUS", + "offset": "0xA8", + "fields": [ + [ + "FH1_CBC_ON", + 0, + 1, + "r" + ], + [ + "FH1_OST_ON", + 1, + 1, + "r" + ] + ] + }, + { + "name": "GEN2_STMP_CFG", + "offset": "0xAC", + "fields": [ + [ + "GEN2_A_UPMETHOD", + 0, + 4 + ], + [ + "GEN2_B_UPMETHOD", + 4, + 4 + ], + [ + "GEN2_A_SHDW_FULL", + 8, + 1 + ], + [ + "GEN2_B_SHDW_FULL", + 9, + 1 + ] + ] + }, + { + "name": "GEN2_TSTMP_A", + "offset": "0xB0", + "fields": [ + [ + "GEN2_A", + 0, + 16 + ] + ] + }, + { + "name": "GEN2_TSTMP_B", + "offset": "0xB4", + "fields": [ + [ + "GEN2_B", + 0, + 16 + ] + ] + }, + { + "name": "GEN2_CFG0", + "offset": "0xB8", + "fields": [ + [ + "GEN2_CFG_UPMETHOD", + 0, + 4 + ], + [ + "GEN2_T0_SEL", + 4, + 3 + ], + [ + "GEN2_T1_SEL", + 7, + 3 + ] + ] + }, + { + "name": "GEN2_FORCE", + "offset": "0xBC", + "reset": "0x00000020", + "fields": [ + [ + "GEN2_CNTUFORCE_UPMETHOD", + 0, + 6 + ], + [ + "GEN2_A_CNTUFORCE_MODE", + 6, + 2 + ], + [ + "GEN2_B_CNTUFORCE_MODE", + 8, + 2 + ], + [ + "GEN2_A_NCIFORCE", + 10, + 1 + ], + [ + "GEN2_A_NCIFORCE_MODE", + 11, + 2 + ], + [ + "GEN2_B_NCIFORCE", + 13, + 1 + ], + [ + "GEN2_B_NCIFORCE_MODE", + 14, + 2 + ] + ] + }, + { + "name": "GEN2_A", + "offset": "0xC0", + "fields": [ + [ + "UTEZ", + 0, + 2 + ], + [ + "UTEP", + 2, + 2 + ], + [ + "UTEA", + 4, + 2 + ], + [ + "UTEB", + 6, + 2 + ], + [ + "UT0", + 8, + 2 + ], + [ + "UT1", + 10, + 2 + ], + [ + "DTEZ", + 12, + 2 + ], + [ + "DTEP", + 14, + 2 + ], + [ + "DTEA", + 16, + 2 + ], + [ + "DTEB", + 18, + 2 + ], + [ + "DT0", + 20, + 2 + ], + [ + "DT1", + 22, + 2 + ] + ] + }, + { + "name": "GEN2_B", + "offset": "0xC4", + "fields": [ + [ + "UTEZ", + 0, + 2 + ], + [ + "UTEP", + 2, + 2 + ], + [ + "UTEA", + 4, + 2 + ], + [ + "UTEB", + 6, + 2 + ], + [ + "UT0", + 8, + 2 + ], + [ + "UT1", + 10, + 2 + ], + [ + "DTEZ", + 12, + 2 + ], + [ + "DTEP", + 14, + 2 + ], + [ + "DTEA", + 16, + 2 + ], + [ + "DTEB", + 18, + 2 + ], + [ + "DT0", + 20, + 2 + ], + [ + "DT1", + 22, + 2 + ] + ] + }, + { + "name": "DT2_CFG", + "offset": "0xC8", + "reset": "0x00018000", + "fields": [ + [ + "DT2_FED_UPMETHOD", + 0, + 4 + ], + [ + "DT2_RED_UPMETHOD", + 4, + 4 + ], + [ + "DT2_DEB_MODE", + 8, + 1 + ], + [ + "DT2_A_OUTSWAP", + 9, + 1 + ], + [ + "DT2_B_OUTSWAP", + 10, + 1 + ], + [ + "DT2_RED_INSEL", + 11, + 1 + ], + [ + "DT2_FED_INSEL", + 12, + 1 + ], + [ + "DT2_RED_OUTINVERT", + 13, + 1 + ], + [ + "DT2_FED_OUTINVERT", + 14, + 1 + ], + [ + "DT2_A_OUTBYPASS", + 15, + 1 + ], + [ + "DT2_B_OUTBYPASS", + 16, + 1 + ], + [ + "DT2_CLK_SEL", + 17, + 1 + ] + ] + }, + { + "name": "DT2_FED_CFG", + "offset": "0xCC", + "fields": [ + [ + "DT2_FED", + 0, + 16 + ] + ] + }, + { + "name": "DT2_RED_CFG", + "offset": "0xD0", + "fields": [ + [ + "DT2_RED", + 0, + 16 + ] + ] + }, + { + "name": "CARRIER2_CFG", + "offset": "0xD4", + "fields": [ + [ + "CARRIER2_EN", + 0, + 1 + ], + [ + "CARRIER2_PRESCALE", + 1, + 4 + ], + [ + "CARRIER2_DUTY", + 5, + 3 + ], + [ + "CARRIER2_OSHTWTH", + 8, + 4 + ], + [ + "CARRIER2_OUT_INVERT", + 12, + 1 + ], + [ + "CARRIER2_IN_INVERT", + 13, + 1 + ] + ] + }, + { + "name": "FH2_CFG0", + "offset": "0xD8", + "fields": [ + [ + "FH2_SW_CBC", + 0, + 1 + ], + [ + "FH2_F2_CBC", + 1, + 1 + ], + [ + "FH2_F1_CBC", + 2, + 1 + ], + [ + "FH2_F0_CBC", + 3, + 1 + ], + [ + "FH2_SW_OST", + 4, + 1 + ], + [ + "FH2_F2_OST", + 5, + 1 + ], + [ + "FH2_F1_OST", + 6, + 1 + ], + [ + "FH2_F0_OST", + 7, + 1 + ], + [ + "FH2_A_CBC_D", + 8, + 2 + ], + [ + "FH2_A_CBC_U", + 10, + 2 + ], + [ + "FH2_A_OST_D", + 12, + 2 + ], + [ + "FH2_A_OST_U", + 14, + 2 + ], + [ + "FH2_B_CBC_D", + 16, + 2 + ], + [ + "FH2_B_CBC_U", + 18, + 2 + ], + [ + "FH2_B_OST_D", + 20, + 2 + ], + [ + "FH2_B_OST_U", + 22, + 2 + ] + ] + }, + { + "name": "FH2_CFG1", + "offset": "0xDC", + "fields": [ + [ + "FH2_CLR_OST", + 0, + 1 + ], + [ + "FH2_CBCPULSE", + 1, + 2 + ], + [ + "FH2_FORCE_CBC", + 3, + 1 + ], + [ + "FH2_FORCE_OST", + 4, + 1 + ] + ] + }, + { + "name": "FH2_STATUS", + "offset": "0xE0", + "fields": [ + [ + "FH2_CBC_ON", + 0, + 1, + "r" + ], + [ + "FH2_OST_ON", + 1, + 1, + "r" + ] + ] + }, + { + "name": "FAULT_DETECT", + "offset": "0xE4", + "fields": [ + [ + "F0_EN", + 0, + 1 + ], + [ + "F1_EN", + 1, + 1 + ], + [ + "F2_EN", + 2, + 1 + ], + [ + "F0_POLE", + 3, + 1 + ], + [ + "F1_POLE", + 4, + 1 + ], + [ + "F2_POLE", + 5, + 1 + ], + [ + "EVENT_F0", + 6, + 1, + "r" + ], + [ + "EVENT_F1", + 7, + 1, + "r" + ], + [ + "EVENT_F2", + 8, + 1, + "r" + ] + ] + }, + { + "name": "CAP_TIMER_CFG", + "offset": "0xE8", + "fields": [ + [ + "CAP_TIMER_EN", + 0, + 1 + ], + [ + "CAP_SYNCI_EN", + 1, + 1 + ], + [ + "CAP_SYNCI_SEL", + 2, + 3 + ], + [ + "CAP_SYNC_SW", + 5, + 1, + "w" + ] + ] + }, + { + "name": "CAP_TIMER_PHASE", + "offset": "0xEC", + "fields": [ + [ + "CAP_TIMER_PHASE", + 0, + 32 + ] + ] + }, + { + "name": "CAP_CH0_CFG", + "offset": "0xF0", + "fields": [ + [ + "CAP0_EN", + 0, + 1 + ], + [ + "CAP0_MODE", + 1, + 2 + ], + [ + "CAP0_PRESCALE", + 3, + 8 + ], + [ + "CAP0_IN_INVERT", + 11, + 1 + ], + [ + "CAP0_SW", + 12, + 1, + "w" + ] + ] + }, + { + "name": "CAP_CH1_CFG", + "offset": "0xF4", + "fields": [ + [ + "CAP1_EN", + 0, + 1 + ], + [ + "CAP1_MODE", + 1, + 2 + ], + [ + "CAP1_PRESCALE", + 3, + 8 + ], + [ + "CAP1_IN_INVERT", + 11, + 1 + ], + [ + "CAP1_SW", + 12, + 1, + "w" + ] + ] + }, + { + "name": "CAP_CH2_CFG", + "offset": "0xF8", + "fields": [ + [ + "CAP2_EN", + 0, + 1 + ], + [ + "CAP2_MODE", + 1, + 2 + ], + [ + "CAP2_PRESCALE", + 3, + 8 + ], + [ + "CAP2_IN_INVERT", + 11, + 1 + ], + [ + "CAP2_SW", + 12, + 1, + "w" + ] + ] + }, + { + "name": "CAP_CH0", + "offset": "0xFC", + "fields": [ + [ + "CAP0_VALUE", + 0, + 32, + "r" + ] + ] + }, + { + "name": "CAP_CH1", + "offset": "0x100", + "fields": [ + [ + "CAP1_VALUE", + 0, + 32, + "r" + ] + ] + }, + { + "name": "CAP_CH2", + "offset": "0x104", + "fields": [ + [ + "CAP2_VALUE", + 0, + 32, + "r" + ] + ] + }, + { + "name": "CAP_STATUS", + "offset": "0x108", + "fields": [ + [ + "CAP0_EDGE", + 0, + 1, + "r" + ], + [ + "CAP1_EDGE", + 1, + 1, + "r" + ], + [ + "CAP2_EDGE", + 2, + 1, + "r" + ] + ] + }, + { + "name": "UPDATE_CFG", + "offset": "0x10C", + "reset": "0x00000055", + "fields": [ + [ + "GLOBAL_UP_EN", + 0, + 1 + ], + [ + "GLOBAL_FORCE_UP", + 1, + 1 + ], + [ + "OP0_UP_EN", + 2, + 1 + ], + [ + "OP0_FORCE_UP", + 3, + 1 + ], + [ + "OP1_UP_EN", + 4, + 1 + ], + [ + "OP1_FORCE_UP", + 5, + 1 + ], + [ + "OP2_UP_EN", + 6, + 1 + ], + [ + "OP2_FORCE_UP", + 7, + 1 + ] + ] + }, + { + "name": "INT_ENA", + "offset": "0x110", + "fields": [ + [ + "TIMER0_STOP_INT_ENA", + 0, + 1 + ], + [ + "TIMER1_STOP_INT_ENA", + 1, + 1 + ], + [ + "TIMER2_STOP_INT_ENA", + 2, + 1 + ], + [ + "TIMER0_TEZ_INT_ENA", + 3, + 1 + ], + [ + "TIMER1_TEZ_INT_ENA", + 4, + 1 + ], + [ + "TIMER2_TEZ_INT_ENA", + 5, + 1 + ], + [ + "TIMER0_TEP_INT_ENA", + 6, + 1 + ], + [ + "TIMER1_TEP_INT_ENA", + 7, + 1 + ], + [ + "TIMER2_TEP_INT_ENA", + 8, + 1 + ], + [ + "FAULT0_INT_ENA", + 9, + 1 + ], + [ + "FAULT1_INT_ENA", + 10, + 1 + ], + [ + "FAULT2_INT_ENA", + 11, + 1 + ], + [ + "FAULT0_CLR_INT_ENA", + 12, + 1 + ], + [ + "FAULT1_CLR_INT_ENA", + 13, + 1 + ], + [ + "FAULT2_CLR_INT_ENA", + 14, + 1 + ], + [ + "OP0_TEA_INT_ENA", + 15, + 1 + ], + [ + "OP1_TEA_INT_ENA", + 16, + 1 + ], + [ + "OP2_TEA_INT_ENA", + 17, + 1 + ], + [ + "OP0_TEB_INT_ENA", + 18, + 1 + ], + [ + "OP1_TEB_INT_ENA", + 19, + 1 + ], + [ + "OP2_TEB_INT_ENA", + 20, + 1 + ], + [ + "FH0_CBC_INT_ENA", + 21, + 1 + ], + [ + "FH1_CBC_INT_ENA", + 22, + 1 + ], + [ + "FH2_CBC_INT_ENA", + 23, + 1 + ], + [ + "FH0_OST_INT_ENA", + 24, + 1 + ], + [ + "FH1_OST_INT_ENA", + 25, + 1 + ], + [ + "FH2_OST_INT_ENA", + 26, + 1 + ], + [ + "CAP0_INT_ENA", + 27, + 1 + ], + [ + "CAP1_INT_ENA", + 28, + 1 + ], + [ + "CAP2_INT_ENA", + 29, + 1 + ] + ] + }, + { + "name": "INT_RAW", + "offset": "0x114", + "fields": [ + [ + "TIMER0_STOP_INT_RAW", + 0, + 1, + "r" + ], + [ + "TIMER1_STOP_INT_RAW", + 1, + 1, + "r" + ], + [ + "TIMER2_STOP_INT_RAW", + 2, + 1, + "r" + ], + [ + "TIMER0_TEZ_INT_RAW", + 3, + 1, + "r" + ], + [ + "TIMER1_TEZ_INT_RAW", + 4, + 1, + "r" + ], + [ + "TIMER2_TEZ_INT_RAW", + 5, + 1, + "r" + ], + [ + "TIMER0_TEP_INT_RAW", + 6, + 1, + "r" + ], + [ + "TIMER1_TEP_INT_RAW", + 7, + 1, + "r" + ], + [ + "TIMER2_TEP_INT_RAW", + 8, + 1, + "r" + ], + [ + "FAULT0_INT_RAW", + 9, + 1, + "r" + ], + [ + "FAULT1_INT_RAW", + 10, + 1, + "r" + ], + [ + "FAULT2_INT_RAW", + 11, + 1, + "r" + ], + [ + "FAULT0_CLR_INT_RAW", + 12, + 1, + "r" + ], + [ + "FAULT1_CLR_INT_RAW", + 13, + 1, + "r" + ], + [ + "FAULT2_CLR_INT_RAW", + 14, + 1, + "r" + ], + [ + "OP0_TEA_INT_RAW", + 15, + 1, + "r" + ], + [ + "OP1_TEA_INT_RAW", + 16, + 1, + "r" + ], + [ + "OP2_TEA_INT_RAW", + 17, + 1, + "r" + ], + [ + "OP0_TEB_INT_RAW", + 18, + 1, + "r" + ], + [ + "OP1_TEB_INT_RAW", + 19, + 1, + "r" + ], + [ + "OP2_TEB_INT_RAW", + 20, + 1, + "r" + ], + [ + "FH0_CBC_INT_RAW", + 21, + 1, + "r" + ], + [ + "FH1_CBC_INT_RAW", + 22, + 1, + "r" + ], + [ + "FH2_CBC_INT_RAW", + 23, + 1, + "r" + ], + [ + "FH0_OST_INT_RAW", + 24, + 1, + "r" + ], + [ + "FH1_OST_INT_RAW", + 25, + 1, + "r" + ], + [ + "FH2_OST_INT_RAW", + 26, + 1, + "r" + ], + [ + "CAP0_INT_RAW", + 27, + 1, + "r" + ], + [ + "CAP1_INT_RAW", + 28, + 1, + "r" + ], + [ + "CAP2_INT_RAW", + 29, + 1, + "r" + ] + ] + }, + { + "name": "INT_ST", + "offset": "0x118", + "fields": [ + [ + "TIMER0_STOP_INT_ST", + 0, + 1, + "r" + ], + [ + "TIMER1_STOP_INT_ST", + 1, + 1, + "r" + ], + [ + "TIMER2_STOP_INT_ST", + 2, + 1, + "r" + ], + [ + "TIMER0_TEZ_INT_ST", + 3, + 1, + "r" + ], + [ + "TIMER1_TEZ_INT_ST", + 4, + 1, + "r" + ], + [ + "TIMER2_TEZ_INT_ST", + 5, + 1, + "r" + ], + [ + "TIMER0_TEP_INT_ST", + 6, + 1, + "r" + ], + [ + "TIMER1_TEP_INT_ST", + 7, + 1, + "r" + ], + [ + "TIMER2_TEP_INT_ST", + 8, + 1, + "r" + ], + [ + "FAULT0_INT_ST", + 9, + 1, + "r" + ], + [ + "FAULT1_INT_ST", + 10, + 1, + "r" + ], + [ + "FAULT2_INT_ST", + 11, + 1, + "r" + ], + [ + "FAULT0_CLR_INT_ST", + 12, + 1, + "r" + ], + [ + "FAULT1_CLR_INT_ST", + 13, + 1, + "r" + ], + [ + "FAULT2_CLR_INT_ST", + 14, + 1, + "r" + ], + [ + "OP0_TEA_INT_ST", + 15, + 1, + "r" + ], + [ + "OP1_TEA_INT_ST", + 16, + 1, + "r" + ], + [ + "OP2_TEA_INT_ST", + 17, + 1, + "r" + ], + [ + "OP0_TEB_INT_ST", + 18, + 1, + "r" + ], + [ + "OP1_TEB_INT_ST", + 19, + 1, + "r" + ], + [ + "OP2_TEB_INT_ST", + 20, + 1, + "r" + ], + [ + "FH0_CBC_INT_ST", + 21, + 1, + "r" + ], + [ + "FH1_CBC_INT_ST", + 22, + 1, + "r" + ], + [ + "FH2_CBC_INT_ST", + 23, + 1, + "r" + ], + [ + "FH0_OST_INT_ST", + 24, + 1, + "r" + ], + [ + "FH1_OST_INT_ST", + 25, + 1, + "r" + ], + [ + "FH2_OST_INT_ST", + 26, + 1, + "r" + ], + [ + "CAP0_INT_ST", + 27, + 1, + "r" + ], + [ + "CAP1_INT_ST", + 28, + 1, + "r" + ], + [ + "CAP2_INT_ST", + 29, + 1, + "r" + ] + ] + }, + { + "name": "INT_CLR", + "offset": "0x11C", + "fields": [ + [ + "TIMER0_STOP_INT_CLR", + 0, + 1, + "w" + ], + [ + "TIMER1_STOP_INT_CLR", + 1, + 1, + "w" + ], + [ + "TIMER2_STOP_INT_CLR", + 2, + 1, + "w" + ], + [ + "TIMER0_TEZ_INT_CLR", + 3, + 1, + "w" + ], + [ + "TIMER1_TEZ_INT_CLR", + 4, + 1, + "w" + ], + [ + "TIMER2_TEZ_INT_CLR", + 5, + 1, + "w" + ], + [ + "TIMER0_TEP_INT_CLR", + 6, + 1, + "w" + ], + [ + "TIMER1_TEP_INT_CLR", + 7, + 1, + "w" + ], + [ + "TIMER2_TEP_INT_CLR", + 8, + 1, + "w" + ], + [ + "FAULT0_INT_CLR", + 9, + 1, + "w" + ], + [ + "FAULT1_INT_CLR", + 10, + 1, + "w" + ], + [ + "FAULT2_INT_CLR", + 11, + 1, + "w" + ], + [ + "FAULT0_CLR_INT_CLR", + 12, + 1, + "w" + ], + [ + "FAULT1_CLR_INT_CLR", + 13, + 1, + "w" + ], + [ + "FAULT2_CLR_INT_CLR", + 14, + 1, + "w" + ], + [ + "OP0_TEA_INT_CLR", + 15, + 1, + "w" + ], + [ + "OP1_TEA_INT_CLR", + 16, + 1, + "w" + ], + [ + "OP2_TEA_INT_CLR", + 17, + 1, + "w" + ], + [ + "OP0_TEB_INT_CLR", + 18, + 1, + "w" + ], + [ + "OP1_TEB_INT_CLR", + 19, + 1, + "w" + ], + [ + "OP2_TEB_INT_CLR", + 20, + 1, + "w" + ], + [ + "FH0_CBC_INT_CLR", + 21, + 1, + "w" + ], + [ + "FH1_CBC_INT_CLR", + 22, + 1, + "w" + ], + [ + "FH2_CBC_INT_CLR", + 23, + 1, + "w" + ], + [ + "FH0_OST_INT_CLR", + 24, + 1, + "w" + ], + [ + "FH1_OST_INT_CLR", + 25, + 1, + "w" + ], + [ + "FH2_OST_INT_CLR", + 26, + 1, + "w" + ], + [ + "CAP0_INT_CLR", + 27, + 1, + "w" + ], + [ + "CAP1_INT_CLR", + 28, + 1, + "w" + ], + [ + "CAP2_INT_CLR", + 29, + 1, + "w" + ] + ] + }, + { + "name": "CLK", + "offset": "0x120", + "fields": [ + [ + "EN", + 0, + 1 + ] + ] + }, + { + "name": "VERSION", + "offset": "0x124", + "reset": "0x02107230", + "fields": [ + [ + "DATE", + 0, + 28 + ] + ] + } + ] + }, + { + "name": "TIMG0", + "base": "0x3FF5F000", + "interrupts": [ + "TG0_T0_LEVEL", + "TG0_T1_LEVEL", + "TG0_WDT_LEVEL", + "TG0_LACT_LEVEL", + "TG0_T0_EDGE", + "TG0_T1_EDGE", + "TG0_WDT_EDGE", + "TG0_LACT_EDGE" + ], + "registers": [ + { + "name": "T0CONFIG", + "offset": "0x00", + "reset": "0x60002000", + "fields": [ + [ + "ALARM_EN", + 10, + 1 + ], + [ + "LEVEL_INT_EN", + 11, + 1 + ], + [ + "EDGE_INT_EN", + 12, + 1 + ], + [ + "DIVIDER", + 13, + 16 + ], + [ + "AUTORELOAD", + 29, + 1 + ], + [ + "INCREASE", + 30, + 1 + ], + [ + "EN", + 31, + 1 + ] + ] + }, + { + "name": "T0LO", + "offset": "0x04", + "fields": [ + [ + "LO", + 0, + 32, + "r" + ] + ] + }, + { + "name": "T0HI", + "offset": "0x08", + "fields": [ + [ + "HI", + 0, + 32, + "r" + ] + ] + }, + { + "name": "T0UPDATE", + "offset": "0x0C", + "fields": [ + [ + "UPDATE", + 0, + 32, + "w" + ] + ] + }, + { + "name": "T0ALARMLO", + "offset": "0x10", + "fields": [ + [ + "ALARM_LO", + 0, + 32 + ] + ] + }, + { + "name": "T0ALARMHI", + "offset": "0x14", + "fields": [ + [ + "ALARM_HI", + 0, + 32 + ] + ] + }, + { + "name": "T0LOADLO", + "offset": "0x18", + "fields": [ + [ + "LOAD_LO", + 0, + 32 + ] + ] + }, + { + "name": "T0LOADHI", + "offset": "0x1C", + "fields": [ + [ + "LOAD_HI", + 0, + 32 + ] + ] + }, + { + "name": "T0LOAD", + "offset": "0x20", + "fields": [ + [ + "LOAD", + 0, + 32, + "w" + ] + ] + }, + { + "name": "T1CONFIG", + "offset": "0x24", + "reset": "0x60002000", + "fields": [ + [ + "ALARM_EN", + 10, + 1 + ], + [ + "LEVEL_INT_EN", + 11, + 1 + ], + [ + "EDGE_INT_EN", + 12, + 1 + ], + [ + "DIVIDER", + 13, + 16 + ], + [ + "AUTORELOAD", + 29, + 1 + ], + [ + "INCREASE", + 30, + 1 + ], + [ + "EN", + 31, + 1 + ] + ] + }, + { + "name": "T1LO", + "offset": "0x28", + "fields": [ + [ + "LO", + 0, + 32, + "r" + ] + ] + }, + { + "name": "T1HI", + "offset": "0x2C", + "fields": [ + [ + "HI", + 0, + 32, + "r" + ] + ] + }, + { + "name": "T1UPDATE", + "offset": "0x30", + "fields": [ + [ + "UPDATE", + 0, + 32, + "w" + ] + ] + }, + { + "name": "T1ALARMLO", + "offset": "0x34", + "fields": [ + [ + "ALARM_LO", + 0, + 32 + ] + ] + }, + { + "name": "T1ALARMHI", + "offset": "0x38", + "fields": [ + [ + "ALARM_HI", + 0, + 32 + ] + ] + }, + { + "name": "T1LOADLO", + "offset": "0x3C", + "fields": [ + [ + "LOAD_LO", + 0, + 32 + ] + ] + }, + { + "name": "T1LOADHI", + "offset": "0x40", + "fields": [ + [ + "LOAD_HI", + 0, + 32 + ] + ] + }, + { + "name": "T1LOAD", + "offset": "0x44", + "fields": [ + [ + "LOAD", + 0, + 32, + "w" + ] + ] + }, + { + "name": "WDTCONFIG0", + "offset": "0x48", + "reset": "0x0004C000", + "fields": [ + [ + "WDT_FLASHBOOT_MOD_EN", + 14, + 1 + ], + [ + "WDT_SYS_RESET_LENGTH", + 15, + 3 + ], + [ + "WDT_CPU_RESET_LENGTH", + 18, + 3 + ], + [ + "WDT_LEVEL_INT_EN", + 21, + 1 + ], + [ + "WDT_EDGE_INT_EN", + 22, + 1 + ], + [ + "WDT_STG3", + 23, + 2 + ], + [ + "WDT_STG2", + 25, + 2 + ], + [ + "WDT_STG1", + 27, + 2 + ], + [ + "WDT_STG0", + 29, + 2 + ], + [ + "WDT_EN", + 31, + 1 + ] + ] + }, + { + "name": "WDTCONFIG1", + "offset": "0x4C", + "reset": "0x00010000", + "fields": [ + [ + "WDT_CLK_PRESCALE", + 16, + 16 + ] + ] + }, + { + "name": "WDTCONFIG2", + "offset": "0x50", + "reset": "0x018CBA80", + "fields": [ + [ + "WDT_STG0_HOLD", + 0, + 32 + ] + ] + }, + { + "name": "WDTCONFIG3", + "offset": "0x54", + "reset": "0x07FFFFFF", + "fields": [ + [ + "WDT_STG1_HOLD", + 0, + 32 + ] + ] + }, + { + "name": "WDTCONFIG4", + "offset": "0x58", + "reset": "0x000FFFFF", + "fields": [ + [ + "WDT_STG2_HOLD", + 0, + 32 + ] + ] + }, + { + "name": "WDTCONFIG5", + "offset": "0x5C", + "reset": "0x000FFFFF", + "fields": [ + [ + "WDT_STG3_HOLD", + 0, + 32 + ] + ] + }, + { + "name": "WDTFEED", + "offset": "0x60", + "fields": [ + [ + "WDT_FEED", + 0, + 32, + "w" + ] + ] + }, + { + "name": "WDTWPROTECT", + "offset": "0x64", + "reset": "0x50D83AA1", + "fields": [ + [ + "WDT_WKEY", + 0, + 32 + ] + ] + }, + { + "name": "RTCCALICFG", + "offset": "0x68", + "reset": "0x00013000", + "fields": [ + [ + "RTC_CALI_START_CYCLING", + 12, + 1 + ], + [ + "RTC_CALI_CLK_SEL", + 13, + 2 + ], + [ + "RTC_CALI_RDY", + 15, + 1, + "r" + ], + [ + "RTC_CALI_MAX", + 16, + 15 + ], + [ + "RTC_CALI_START", + 31, + 1 + ] + ] + }, + { + "name": "RTCCALICFG1", + "offset": "0x6C", + "fields": [ + [ + "RTC_CALI_VALUE", + 7, + 25, + "r" + ] + ] + }, + { + "name": "LACTCONFIG", + "offset": "0x70", + "reset": "0x60002300", + "fields": [ + [ + "LACT_RTC_ONLY", + 7, + 1 + ], + [ + "LACT_CPST_EN", + 8, + 1 + ], + [ + "LACT_LAC_EN", + 9, + 1 + ], + [ + "LACT_ALARM_EN", + 10, + 1 + ], + [ + "LACT_LEVEL_INT_EN", + 11, + 1 + ], + [ + "LACT_EDGE_INT_EN", + 12, + 1 + ], + [ + "LACT_DIVIDER", + 13, + 16 + ], + [ + "LACT_AUTORELOAD", + 29, + 1 + ], + [ + "LACT_INCREASE", + 30, + 1 + ], + [ + "LACT_EN", + 31, + 1 + ] + ] + }, + { + "name": "LACTRTC", + "offset": "0x74", + "fields": [ + [ + "LACT_RTC_STEP_LEN", + 6, + 26 + ] + ] + }, + { + "name": "LACTLO", + "offset": "0x78", + "fields": [ + [ + "LACT_LO", + 0, + 32, + "r" + ] + ] + }, + { + "name": "LACTHI", + "offset": "0x7C", + "fields": [ + [ + "LACT_HI", + 0, + 32, + "r" + ] + ] + }, + { + "name": "LACTUPDATE", + "offset": "0x80", + "fields": [ + [ + "LACT_UPDATE", + 0, + 32, + "w" + ] + ] + }, + { + "name": "LACTALARMLO", + "offset": "0x84", + "fields": [ + [ + "LACT_ALARM_LO", + 0, + 32 + ] + ] + }, + { + "name": "LACTALARMHI", + "offset": "0x88", + "fields": [ + [ + "LACT_ALARM_HI", + 0, + 32 + ] + ] + }, + { + "name": "LACTLOADLO", + "offset": "0x8C", + "fields": [ + [ + "LACT_LOAD_LO", + 0, + 32 + ] + ] + }, + { + "name": "LACTLOADHI", + "offset": "0x90", + "fields": [ + [ + "LACT_LOAD_HI", + 0, + 32 + ] + ] + }, + { + "name": "LACTLOAD", + "offset": "0x94", + "fields": [ + [ + "LACT_LOAD", + 0, + 32, + "w" + ] + ] + }, + { + "name": "INT_ENA_TIMERS", + "offset": "0x98", + "fields": [ + [ + "T0_INT_ENA", + 0, + 1 + ], + [ + "T1_INT_ENA", + 1, + 1 + ], + [ + "WDT_INT_ENA", + 2, + 1 + ], + [ + "LACT_INT_ENA", + 3, + 1 + ] + ] + }, + { + "name": "INT_RAW_TIMERS", + "offset": "0x9C", + "fields": [ + [ + "T0_INT_RAW", + 0, + 1, + "r" + ], + [ + "T1_INT_RAW", + 1, + 1, + "r" + ], + [ + "WDT_INT_RAW", + 2, + 1, + "r" + ], + [ + "LACT_INT_RAW", + 3, + 1, + "r" + ] + ] + }, + { + "name": "INT_ST_TIMERS", + "offset": "0xA0", + "fields": [ + [ + "T0_INT_ST", + 0, + 1, + "r" + ], + [ + "T1_INT_ST", + 1, + 1, + "r" + ], + [ + "WDT_INT_ST", + 2, + 1, + "r" + ], + [ + "LACT_INT_ST", + 3, + 1, + "r" + ] + ] + }, + { + "name": "INT_CLR_TIMERS", + "offset": "0xA4", + "fields": [ + [ + "T0_INT_CLR", + 0, + 1, + "w" + ], + [ + "T1_INT_CLR", + 1, + 1, + "w" + ], + [ + "WDT_INT_CLR", + 2, + 1, + "w" + ], + [ + "LACT_INT_CLR", + 3, + 1, + "w" + ] + ] + }, + { + "name": "NTIMERS_DATE", + "offset": "0xF8", + "reset": "0x01604290", + "fields": [ + [ + "NTIMERS_DATE", + 0, + 28 + ] + ] + }, + { + "name": "TIMGCLK", + "offset": "0xFC", + "fields": [ + [ + "CLK_EN", + 31, + 1 + ] + ] + } + ] + }, + { + "name": "TIMG1", + "base": "0x3FF60000", + "interrupts": [ + "TG1_T0_LEVEL", + "TG1_T1_LEVEL", + "TG1_WDT_LEVEL", + "TG1_LACT_LEVEL", + "TG1_T0_EDGE", + "TG1_T1_EDGE", + "TG1_WDT_EDGE", + "TG1_LACT_EDGE" + ], + "derived_from": "TIMG0" + }, + { + "name": "SPI2", + "base": "0x3FF64000", + "interrupts": [ + "SPI2", + "SPI2_DMA" + ], + "derived_from": "SPI0" + }, + { + "name": "SPI3", + "base": "0x3FF65000", + "interrupts": [ + "SPI3", + "SPI3_DMA" + ], + "derived_from": "SPI0" + }, + { + "name": "APB_CTRL", + "base": "0x3FF66000", + "registers": [ + { + "name": "SYSCLK_CONF", + "offset": "0x00", + "reset": "0x00002000", + "fields": [ + [ + "PRE_DIV_CNT", + 0, + 10 + ], + [ + "CLK_320M_EN", + 10, + 1 + ], + [ + "CLK_EN", + 11, + 1 + ], + [ + "RST_TICK_CNT", + 12, + 1 + ], + [ + "QUICK_CLK_CHNG", + 13, + 1 + ] + ] + }, + { + "name": "XTAL_TICK_CONF", + "offset": "0x04", + "reset": "0x00000027", + "fields": [ + [ + "XTAL_TICK_NUM", + 0, + 8 + ] + ] + }, + { + "name": "PLL_TICK_CONF", + "offset": "0x08", + "reset": "0x0000004F", + "fields": [ + [ + "PLL_TICK_NUM", + 0, + 8 + ] + ] + }, + { + "name": "CK8M_TICK_CONF", + "offset": "0x0C", + "reset": "0x0000000B", + "fields": [ + [ + "CK8M_TICK_NUM", + 0, + 8 + ] + ] + }, + { + "name": "APB_SARADC_CTRL", + "offset": "0x10", + "reset": "0x007F8240", + "fields": [ + [ + "SARADC_START_FORCE", + 0, + 1 + ], + [ + "SARADC_START", + 1, + 1 + ], + [ + "SARADC_SAR2_MUX", + 2, + 1 + ], + [ + "SARADC_WORK_MODE", + 3, + 2 + ], + [ + "SARADC_SAR_SEL", + 5, + 1 + ], + [ + "SARADC_SAR_CLK_GATED", + 6, + 1 + ], + [ + "SARADC_SAR_CLK_DIV", + 7, + 8 + ], + [ + "SARADC_SAR1_PATT_LEN", + 15, + 4 + ], + [ + "SARADC_SAR2_PATT_LEN", + 19, + 4 + ], + [ + "SARADC_SAR1_PATT_P_CLEAR", + 23, + 1 + ], + [ + "SARADC_SAR2_PATT_P_CLEAR", + 24, + 1 + ], + [ + "SARADC_DATA_SAR_SEL", + 25, + 1 + ], + [ + "SARADC_DATA_TO_I2S", + 26, + 1 + ] + ] + }, + { + "name": "APB_SARADC_CTRL2", + "offset": "0x14", + "reset": "0x000001FE", + "fields": [ + [ + "SARADC_MEAS_NUM_LIMIT", + 0, + 1 + ], + [ + "SARADC_MAX_MEAS_NUM", + 1, + 8 + ], + [ + "SARADC_SAR1_INV", + 9, + 1 + ], + [ + "SARADC_SAR2_INV", + 10, + 1 + ] + ] + }, + { + "name": "APB_SARADC_FSM", + "offset": "0x18", + "reset": "0x0208FF08", + "fields": [ + [ + "SARADC_RSTB_WAIT", + 0, + 8 + ], + [ + "SARADC_STANDBY_WAIT", + 8, + 8 + ], + [ + "SARADC_START_WAIT", + 16, + 8 + ], + [ + "SARADC_SAMPLE_CYCLE", + 24, + 8 + ] + ] + }, + { + "name": "APB_SARADC_SAR1_PATT_TAB1", + "offset": "0x1C", + "reset": "0x0F0F0F0F", + "fields": [ + [ + "SARADC_SAR1_PATT_TAB1", + 0, + 32 + ] + ] + }, + { + "name": "APB_SARADC_SAR1_PATT_TAB2", + "offset": "0x20", + "reset": "0x0F0F0F0F", + "fields": [ + [ + "SARADC_SAR1_PATT_TAB1", + 0, + 32 + ] + ] + }, + { + "name": "APB_SARADC_SAR1_PATT_TAB3", + "offset": "0x24", + "reset": "0x0F0F0F0F", + "fields": [ + [ + "SARADC_SAR1_PATT_TAB1", + 0, + 32 + ] + ] + }, + { + "name": "APB_SARADC_SAR1_PATT_TAB4", + "offset": "0x28", + "reset": "0x0F0F0F0F", + "fields": [ + [ + "SARADC_SAR1_PATT_TAB1", + 0, + 32 + ] + ] + }, + { + "name": "APB_SARADC_SAR2_PATT_TAB1", + "offset": "0x2C", + "reset": "0x0F0F0F0F", + "fields": [ + [ + "SARADC_SAR2_PATT_TAB1", + 0, + 32 + ] + ] + }, + { + "name": "APB_SARADC_SAR2_PATT_TAB2", + "offset": "0x30", + "reset": "0x0F0F0F0F", + "fields": [ + [ + "SARADC_SAR2_PATT_TAB1", + 0, + 32 + ] + ] + }, + { + "name": "APB_SARADC_SAR2_PATT_TAB3", + "offset": "0x34", + "reset": "0x0F0F0F0F", + "fields": [ + [ + "SARADC_SAR2_PATT_TAB1", + 0, + 32 + ] + ] + }, + { + "name": "APB_SARADC_SAR2_PATT_TAB4", + "offset": "0x38", + "reset": "0x0F0F0F0F", + "fields": [ + [ + "SARADC_SAR2_PATT_TAB1", + 0, + 32 + ] + ] + }, + { + "name": "APLL_TICK_CONF", + "offset": "0x3C", + "reset": "0x00000063", + "fields": [ + [ + "APLL_TICK_NUM", + 0, + 8 + ] + ] + }, + { + "name": "DATE", + "offset": "0x7C", + "reset": "0x16042000", + "fields": [ + [ + "DATE", + 0, + 32 + ] + ] + } + ] + }, + { + "name": "I2C1", + "base": "0x3FF67000", + "interrupts": [ + "I2C_EXT1" + ], + "derived_from": "I2C0" + }, + { + "name": "SDHOST", + "base": "0x3FF68000", + "registers": [ + { + "name": "CTRL", + "offset": "0x00", + "fields": [ + [ + "CONTROLLER_RESET", + 0, + 1 + ], + [ + "FIFO_RESET", + 1, + 1 + ], + [ + "DMA_RESET", + 2, + 1 + ], + [ + "INT_ENABLE", + 4, + 1 + ], + [ + "READ_WAIT", + 6, + 1 + ], + [ + "SEND_IRQ_RESPONSE", + 7, + 1 + ], + [ + "ABORT_READ_DATA", + 8, + 1 + ], + [ + "SEND_CCSD", + 9, + 1 + ], + [ + "SEND_AUTO_STOP_CCSD", + 10, + 1 + ], + [ + "CEATA_DEVICE_INTERRUPT_STATUS", + 11, + 1 + ] + ] + }, + { + "name": "CLKDIV", + "offset": "0x08", + "fields": [ + [ + "CLK_DIVIDER0", + 0, + 8 + ], + [ + "CLK_DIVIDER1", + 8, + 8 + ], + [ + "CLK_DIVIDER2", + 16, + 8 + ], + [ + "CLK_DIVIDER3", + 24, + 8 + ] + ] + }, + { + "name": "CLKSRC", + "offset": "0x0C", + "fields": [ + [ + "CLKSRC", + 0, + 4 + ] + ] + }, + { + "name": "CLKENA", + "offset": "0x10", + "fields": [ + [ + "CCLK_ENABLE", + 0, + 2 + ], + [ + "LP_ENABLE", + 16, + 2 + ] + ] + }, + { + "name": "TMOUT", + "offset": "0x14", + "reset": "0xFFFFFF40", + "fields": [ + [ + "RESPONSE_TIMEOUT", + 0, + 8 + ], + [ + "DATA_TIMEOUT", + 8, + 24 + ] + ] + }, + { + "name": "CTYPE", + "offset": "0x18", + "fields": [ + [ + "CARD_WIDTH4", + 0, + 2 + ], + [ + "CARD_WIDTH8", + 16, + 2 + ] + ] + }, + { + "name": "BLKSIZ", + "offset": "0x1C", + "reset": "0x00000200", + "fields": [ + [ + "BLOCK_SIZE", + 0, + 16 + ] + ] + }, + { + "name": "BYTCNT", + "offset": "0x20", + "reset": "0x00000200", + "fields": [ + [ + "BYTE_COUNT", + 0, + 32 + ] + ] + }, + { + "name": "INTMASK", + "offset": "0x24", + "fields": [ + [ + "INT_MASK", + 0, + 16 + ], + [ + "SDIO_INT_MASK", + 16, + 2 + ] + ] + }, + { + "name": "CMDARG", + "offset": "0x28", + "fields": [ + [ + "CMDARG", + 0, + 32 + ] + ] + }, + { + "name": "CMD", + "offset": "0x2C", + "reset": "0x20000000", + "fields": [ + [ + "INDEX", + 0, + 6 + ], + [ + "RESPONSE_EXPECT", + 6, + 1 + ], + [ + "RESPONSE_LENGTH", + 7, + 1 + ], + [ + "CHECK_RESPONSE_CRC", + 8, + 1 + ], + [ + "DATA_EXPECTED", + 9, + 1 + ], + [ + "READ_WRITE", + 10, + 1 + ], + [ + "TRANSFER_MODE", + 11, + 1 + ], + [ + "SEND_AUTO_STOP", + 12, + 1 + ], + [ + "WAIT_PRVDATA_COMPLETE", + 13, + 1 + ], + [ + "STOP_ABORT_CMD", + 14, + 1 + ], + [ + "SEND_INITIALIZATION", + 15, + 1 + ], + [ + "CARD_NUMBER", + 16, + 5 + ], + [ + "UPDATE_CLOCK_REGISTERS_ONLY", + 21, + 1 + ], + [ + "READ_CEATA_DEVICE", + 22, + 1 + ], + [ + "CCS_EXPECTED", + 23, + 1 + ], + [ + "USE_HOLE", + 29, + 1 + ], + [ + "START_CMD", + 31, + 1 + ] + ] + }, + { + "name": "RESP0", + "offset": "0x30", + "fields": [ + [ + "RESPONSE0", + 0, + 32, + "r" + ] + ] + }, + { + "name": "RESP1", + "offset": "0x34", + "fields": [ + [ + "RESPONSE1", + 0, + 32, + "r" + ] + ] + }, + { + "name": "RESP2", + "offset": "0x38", + "fields": [ + [ + "RESPONSE2", + 0, + 32, + "r" + ] + ] + }, + { + "name": "RESP3", + "offset": "0x3C", + "fields": [ + [ + "RESPONSE3", + 0, + 32, + "r" + ] + ] + }, + { + "name": "MINTSTS", + "offset": "0x40", + "fields": [ + [ + "INT_STATUS_MSK", + 0, + 16, + "r" + ], + [ + "SDIO_INTERRUPT_MSK", + 16, + 2, + "r" + ] + ] + }, + { + "name": "RINTSTS", + "offset": "0x44", + "fields": [ + [ + "INT_STATUS_RAW", + 0, + 16 + ], + [ + "SDIO_INTERRUPT_RAW", + 16, + 2 + ] + ] + }, + { + "name": "STATUS", + "offset": "0x48", + "reset": "0x00000716", + "fields": [ + [ + "FIFO_RX_WATERMARK", + 0, + 1, + "r" + ], + [ + "FIFO_TX_WATERMARK", + 1, + 1, + "r" + ], + [ + "FIFO_EMPTY", + 2, + 1, + "r" + ], + [ + "FIFO_FULL", + 3, + 1, + "r" + ], + [ + "COMMAND_FSM_STATES", + 4, + 4, + "r" + ], + [ + "DATA_3_STATUS", + 8, + 1, + "r" + ], + [ + "DATA_BUSY", + 9, + 1, + "r" + ], + [ + "DATA_STATE_MC_BUSY", + 10, + 1, + "r" + ], + [ + "RESPONSE_INDEX", + 11, + 6, + "r" + ], + [ + "FIFO_COUNT", + 17, + 13, + "r" + ] + ] + }, + { + "name": "FIFOTH", + "offset": "0x4C", + "fields": [ + [ + "TX_WMARK", + 0, + 12 + ], + [ + "RX_WMARK", + 16, + 11 + ], + [ + "DMA_MULTIPLE_TRANSACTION_SIZE", + 28, + 3 + ] + ] + }, + { + "name": "CDETECT", + "offset": "0x50", + "fields": [ + [ + "CARD_DETECT_N", + 0, + 2, + "r" + ] + ] + }, + { + "name": "WRTPRT", + "offset": "0x54", + "fields": [ + [ + "WRITE_PROTECT", + 0, + 2, + "r" + ] + ] + }, + { + "name": "TCBCNT", + "offset": "0x5C", + "fields": [ + [ + "TCBCNT", + 0, + 32, + "r" + ] + ] + }, + { + "name": "TBBCNT", + "offset": "0x60", + "fields": [ + [ + "TBBCNT", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DEBNCE", + "offset": "0x64", + "fields": [ + [ + "DEBOUNCE_COUNT", + 0, + 24 + ] + ] + }, + { + "name": "USRID", + "offset": "0x68", + "fields": [ + [ + "USRID", + 0, + 32 + ] + ] + }, + { + "name": "VERID", + "offset": "0x6C", + "reset": "0x5432270A", + "fields": [ + [ + "VERSIONID", + 0, + 32, + "r" + ] + ] + }, + { + "name": "HCON", + "offset": "0x70", + "reset": "0x03444CC3", + "fields": [ + [ + "CARD_TYPE", + 0, + 1, + "r" + ], + [ + "CARD_NUM", + 1, + 5, + "r" + ], + [ + "BUS_TYPE", + 6, + 1, + "r" + ], + [ + "DATA_WIDTH", + 7, + 3, + "r" + ], + [ + "ADDR_WIDTH", + 10, + 6, + "r" + ], + [ + "DMA_WIDTH", + 18, + 3, + "r" + ], + [ + "RAM_INDISE", + 21, + 1, + "r" + ], + [ + "HOLD", + 22, + 1, + "r" + ], + [ + "NUM_CLK_DIV", + 24, + 2, + "r" + ] + ] + }, + { + "name": "UHS", + "offset": "0x74", + "fields": [ + [ + "DDR", + 16, + 2 + ] + ] + }, + { + "name": "RST_N", + "offset": "0x78", + "reset": "0x00000001", + "fields": [ + [ + "CARD_RESET", + 0, + 2 + ] + ] + }, + { + "name": "BMOD", + "offset": "0x80", + "fields": [ + [ + "SWR", + 0, + 1 + ], + [ + "FB", + 1, + 1 + ], + [ + "DE", + 7, + 1 + ], + [ + "PBL", + 8, + 3 + ] + ] + }, + { + "name": "PLDMND", + "offset": "0x84", + "fields": [ + [ + "PD", + 0, + 32, + "w" + ] + ] + }, + { + "name": "DBADDR", + "offset": "0x88", + "fields": [ + [ + "DBADDR", + 0, + 32 + ] + ] + }, + { + "name": "IDSTS", + "offset": "0x8C", + "fields": [ + [ + "TI", + 0, + 1 + ], + [ + "RI", + 1, + 1 + ], + [ + "FBE", + 2, + 1 + ], + [ + "DU", + 4, + 1 + ], + [ + "CES", + 5, + 1 + ], + [ + "NIS", + 8, + 1 + ], + [ + "AIS", + 9, + 1 + ], + [ + "FBE_CODE", + 10, + 3 + ], + [ + "FSM", + 13, + 4 + ] + ] + }, + { + "name": "IDINTEN", + "offset": "0x90", + "fields": [ + [ + "TI", + 0, + 1 + ], + [ + "RI", + 1, + 1 + ], + [ + "FBE", + 2, + 1 + ], + [ + "DU", + 4, + 1 + ], + [ + "CES", + 5, + 1 + ], + [ + "NI", + 8, + 1 + ], + [ + "AI", + 9, + 1 + ] + ] + }, + { + "name": "DSCADDR", + "offset": "0x94", + "fields": [ + [ + "DSCADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "BUFADDR", + "offset": "0x98", + "fields": [ + [ + "BUFADDR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "CARDTHRCTL", + "offset": "0x100", + "fields": [ + [ + "CARDRDTHREN", + 0, + 1 + ], + [ + "CARDCLRINTEN", + 1, + 1 + ], + [ + "CARDWRTHREN", + 2, + 1 + ], + [ + "CARDTHRESHOLD", + 16, + 16 + ] + ] + }, + { + "name": "EMMCDDR", + "offset": "0x10C", + "fields": [ + [ + "HALFSTARTBIT", + 0, + 2 + ], + [ + "HS400_MODE", + 31, + 1 + ] + ] + }, + { + "name": "ENSHIFT", + "offset": "0x110", + "fields": [ + [ + "ENABLE_SHIFT", + 0, + 4 + ] + ] + }, + { + "name": "BUFFIFO", + "offset": "0x200", + "fields": [ + [ + "BUFFIFO", + 0, + 32 + ] + ] + }, + { + "name": "CLK_EDGE_SEL", + "offset": "0x800", + "reset": "0x00820200", + "fields": [ + [ + "CCLKIN_EDGE_DRV_SEL", + 0, + 3 + ], + [ + "CCLKIN_EDGE_SAM_SEL", + 3, + 3 + ], + [ + "CCLKIN_EDGE_SLF_SEL", + 6, + 3 + ], + [ + "CCLLKIN_EDGE_H", + 9, + 4 + ], + [ + "CCLLKIN_EDGE_L", + 13, + 4 + ], + [ + "CCLLKIN_EDGE_N", + 17, + 4 + ], + [ + "ESDIO_MODE", + 21, + 1 + ], + [ + "ESD_MODE", + 22, + 1 + ], + [ + "CCLK_EN", + 23, + 1 + ] + ] + } + ] + }, + { + "name": "EMAC_DMA", + "base": "0x3FF69000", + "registers": [ + { + "name": "DMABUSMODE", + "offset": "0x00", + "fields": [ + [ + "SW_RST", + 0, + 1 + ], + [ + "DMA_ARB_SCH", + 1, + 1 + ], + [ + "DESC_SKIP_LEN", + 2, + 5 + ], + [ + "ALT_DESC_SIZE", + 7, + 1 + ], + [ + "PROG_BURST_LEN", + 8, + 6 + ], + [ + "PRI_RATIO", + 14, + 2 + ], + [ + "FIXED_BURST", + 16, + 1 + ], + [ + "RX_DMA_PBL", + 17, + 6 + ], + [ + "USE_SEP_PBL", + 23, + 1 + ], + [ + "PBLX8_MODE", + 24, + 1 + ], + [ + "DMAADDRALIBEA", + 25, + 1 + ], + [ + "DMAMIXEDBURST", + 26, + 1 + ] + ] + }, + { + "name": "DMATXPOLLDEMAND", + "offset": "0x04", + "fields": [ + [ + "DMATXPOLLDEMAND", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DMARXPOLLDEMAND", + "offset": "0x08", + "fields": [ + [ + "DMARXPOLLDEMAND", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DMARXBASEADDR", + "offset": "0x0C" + }, + { + "name": "DMATXBASEADDR", + "offset": "0x10" + }, + { + "name": "DMASTATUS", + "offset": "0x14", + "fields": [ + [ + "TRANS_INT", + 0, + 1 + ], + [ + "TRANS_PROC_STOP", + 1, + 1 + ], + [ + "TRANS_BUF_UNAVAIL", + 2, + 1 + ], + [ + "TRANS_JABBER_TO", + 3, + 1 + ], + [ + "RECV_OVFLOW", + 4, + 1 + ], + [ + "TRANS_UNDFLOW", + 5, + 1 + ], + [ + "RECV_INT", + 6, + 1 + ], + [ + "RECV_BUF_UNAVAIL", + 7, + 1 + ], + [ + "RECV_PROC_STOP", + 8, + 1 + ], + [ + "RECV_WDT_TO", + 9, + 1 + ], + [ + "EARLY_TRANS_INT", + 10, + 1 + ], + [ + "FATAL_BUS_ERR_INT", + 13, + 1 + ], + [ + "EARLY_RECV_INT", + 14, + 1 + ], + [ + "ABN_INT_SUMM", + 15, + 1 + ], + [ + "NORM_INT_SUMM", + 16, + 1 + ], + [ + "RECV_PROC_STATE", + 17, + 3 + ], + [ + "TRANS_PROC_STATE", + 20, + 3 + ], + [ + "ERROR_BITS", + 23, + 3 + ], + [ + "PMT_INT", + 28, + 1 + ], + [ + "TS_TRI_INT", + 29, + 1 + ] + ] + }, + { + "name": "DMAOPERATION_MODE", + "offset": "0x18", + "fields": [ + [ + "START_STOP_RX", + 1, + 1 + ], + [ + "OPT_SECOND_FRAME", + 2, + 1 + ], + [ + "RX_THRESH_CTRL", + 3, + 2 + ], + [ + "DROP_GFRM", + 5, + 1 + ], + [ + "FWD_UNDER_GF", + 6, + 1 + ], + [ + "FWD_ERR_FRAME", + 7, + 1 + ], + [ + "START_STOP_TRANSMISSION_COMMAND", + 13, + 1 + ], + [ + "TX_THRESH_CTRL", + 14, + 3 + ], + [ + "FLUSH_TX_FIFO", + 20, + 1 + ], + [ + "TX_STR_FWD", + 21, + 1 + ], + [ + "DIS_FLUSH_RECV_FRAMES", + 24, + 1 + ], + [ + "RX_STORE_FORWARD", + 25, + 1 + ], + [ + "DIS_DROP_TCPIP_ERR_FRAM", + 26, + 1 + ] + ] + }, + { + "name": "DMAIN_EN", + "offset": "0x1C", + "fields": [ + [ + "DMAIN_TIE", + 0, + 1 + ], + [ + "DMAIN_TSE", + 1, + 1 + ], + [ + "DMAIN_TBUE", + 2, + 1 + ], + [ + "DMAIN_TJTE", + 3, + 1 + ], + [ + "DMAIN_OIE", + 4, + 1 + ], + [ + "DMAIN_UIE", + 5, + 1 + ], + [ + "DMAIN_RIE", + 6, + 1 + ], + [ + "DMAIN_RBUE", + 7, + 1 + ], + [ + "DMAIN_RSE", + 8, + 1 + ], + [ + "DMAIN_RWTE", + 9, + 1 + ], + [ + "DMAIN_ETIE", + 10, + 1 + ], + [ + "DMAIN_FBEE", + 13, + 1 + ], + [ + "DMAIN_ERIE", + 14, + 1 + ], + [ + "DMAIN_AISE", + 15, + 1 + ], + [ + "DMAIN_NISE", + 16, + 1 + ] + ] + }, + { + "name": "DMAMISSEDFR", + "offset": "0x20", + "fields": [ + [ + "MISSED_FC", + 0, + 16 + ], + [ + "OVERFLOW_BMFC", + 16, + 1 + ], + [ + "OVERFLOW_FC", + 17, + 11 + ], + [ + "OVERFLOW_BFOC", + 28, + 1 + ] + ] + }, + { + "name": "DMARINTWDTIMER", + "offset": "0x24", + "fields": [ + [ + "RIWTC", + 0, + 8 + ] + ] + }, + { + "name": "DMATXCURRDESC", + "offset": "0x48", + "fields": [ + [ + "DMATXCURRDESC", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DMARXCURRDESC", + "offset": "0x4C", + "fields": [ + [ + "DMARXCURRDESC", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DMATXCURRADDR_BUF", + "offset": "0x50", + "fields": [ + [ + "DMATXCURRADDR_BUF", + 0, + 32, + "r" + ] + ] + }, + { + "name": "DMARXCURRADDR_BUF", + "offset": "0x54", + "fields": [ + [ + "DMARXCURRADDR_BUF", + 0, + 32, + "r" + ] + ] + } + ] + }, + { + "name": "EMAC_EXT", + "base": "0x3FF69800", + "registers": [ + { + "name": "EX_CLKOUT_CONF", + "offset": "0x00", + "fields": [ + [ + "DIV_NUM", + 0, + 4 + ], + [ + "H_DIV_NUM", + 4, + 4 + ], + [ + "DLY_NUM", + 8, + 2 + ] + ] + }, + { + "name": "EX_OSCCLK_CONF", + "offset": "0x04", + "fields": [ + [ + "DIV_NUM_10M", + 0, + 6 + ], + [ + "H_DIV_NUM_10M", + 6, + 6 + ], + [ + "DIV_NUM_100M", + 12, + 6 + ], + [ + "H_DIV_NUM_100M", + 18, + 6 + ], + [ + "CLK_SEL", + 24, + 1 + ] + ] + }, + { + "name": "EX_CLK_CTRL", + "offset": "0x08", + "fields": [ + [ + "EXT_EN", + 0, + 1 + ], + [ + "INT_EN", + 1, + 1 + ], + [ + "RX_125_CLK_EN", + 2, + 1 + ], + [ + "MII_CLK_TX_EN", + 3, + 1 + ], + [ + "MII_CLK_RX_EN", + 4, + 1 + ], + [ + "CLK_EN", + 5, + 1 + ] + ] + }, + { + "name": "EX_PHYINF_CONF", + "offset": "0x0C", + "fields": [ + [ + "INT_REVMII_RX_CLK_SEL", + 0, + 1 + ], + [ + "EXT_REVMII_RX_CLK_SEL", + 1, + 1 + ], + [ + "SBD_FLOWCTRL", + 2, + 1 + ], + [ + "CORE_PHY_ADDR", + 3, + 5 + ], + [ + "REVMII_PHY_ADDR", + 8, + 5 + ], + [ + "PHY_INTF_SEL", + 13, + 3 + ], + [ + "SS_MODE", + 16, + 1 + ], + [ + "SBD_CLK_GATING_EN", + 17, + 1 + ], + [ + "PMT_CTRL_EN", + 18, + 1 + ], + [ + "SCR_SMI_DLY_RX_SYNC", + 19, + 1 + ], + [ + "TX_ERR_OUT_EN", + 20, + 1 + ] + ] + }, + { + "name": "PD_SEL", + "offset": "0x10", + "fields": [ + [ + "RAM_PD_EN", + 0, + 2 + ] + ] + }, + { + "name": "EX_DATE", + "offset": "0xFC" + } + ] + }, + { + "name": "EMAC_MAC", + "base": "0x3FF6A000", + "registers": [ + { + "name": "EMACCONFIG", + "offset": "0x00", + "fields": [ + [ + "PLTF", + 0, + 2 + ], + [ + "RX", + 2, + 1 + ], + [ + "TX", + 3, + 1 + ], + [ + "DEFERRALCHECK", + 4, + 1 + ], + [ + "BACKOFFLIMIT", + 5, + 2 + ], + [ + "PADCRCSTRIP", + 7, + 1 + ], + [ + "RETRY", + 9, + 1 + ], + [ + "RXIPCOFFLOAD", + 10, + 1 + ], + [ + "DUPLEX", + 11, + 1 + ], + [ + "LOOPBACK", + 12, + 1 + ], + [ + "RXOWN", + 13, + 1 + ], + [ + "FESPEED", + 14, + 1 + ], + [ + "MII", + 15, + 1 + ], + [ + "DISABLECRS", + 16, + 1 + ], + [ + "INTERFRAMEGAP", + 17, + 3 + ], + [ + "JUMBOFRAME", + 20, + 1 + ], + [ + "JABBER", + 22, + 1 + ], + [ + "WATCHDOG", + 23, + 1 + ], + [ + "ASS2KP", + 27, + 1 + ], + [ + "SAIRC", + 28, + 3 + ] + ] + }, + { + "name": "EMACFF", + "offset": "0x04", + "fields": [ + [ + "PMODE", + 0, + 1 + ], + [ + "DAIF", + 3, + 1 + ], + [ + "PAM", + 4, + 1 + ], + [ + "DBF", + 5, + 1 + ], + [ + "PCF", + 6, + 2 + ], + [ + "SAIF", + 8, + 1 + ], + [ + "SAFE", + 9, + 1 + ], + [ + "RECEIVE_ALL", + 31, + 1 + ] + ] + }, + { + "name": "EMACGMIIADDR", + "offset": "0x10", + "fields": [ + [ + "MIIBUSY", + 0, + 1 + ], + [ + "MIIWRITE", + 1, + 1 + ], + [ + "MIICSRCLK", + 2, + 4 + ], + [ + "MIIREG", + 6, + 5 + ], + [ + "MIIDEV", + 11, + 5 + ] + ] + }, + { + "name": "EMACMIIDATA", + "offset": "0x14", + "fields": [ + [ + "MII_DATA", + 0, + 16 + ] + ] + }, + { + "name": "EMACFC", + "offset": "0x18", + "fields": [ + [ + "FCBBA", + 0, + 1 + ], + [ + "TFCE", + 1, + 1 + ], + [ + "RFCE", + 2, + 1 + ], + [ + "UPFD", + 3, + 1 + ], + [ + "PLT", + 4, + 2 + ], + [ + "DZPQ", + 7, + 1 + ], + [ + "PAUSE_TIME", + 16, + 16 + ] + ] + }, + { + "name": "EMACDEBUG", + "offset": "0x24", + "fields": [ + [ + "MACRPES", + 0, + 1 + ], + [ + "MACRFFCS", + 1, + 2 + ], + [ + "MTLRFWCAS", + 4, + 1 + ], + [ + "MTLRFRCS", + 5, + 2 + ], + [ + "MTLRFFLS", + 8, + 2 + ], + [ + "MACTPES", + 16, + 1 + ], + [ + "MACTFCS", + 17, + 2 + ], + [ + "MACTP", + 19, + 1 + ], + [ + "MTLTFRCS", + 20, + 2 + ], + [ + "MTLTFWCS", + 22, + 1 + ], + [ + "MTLTFNES", + 24, + 1 + ], + [ + "MTLTSFFS", + 25, + 1 + ] + ] + }, + { + "name": "PMT_RWUFFR", + "offset": "0x28", + "fields": [ + [ + "PMT_RWUFFR", + 0, + 32, + "r" + ] + ] + }, + { + "name": "PMT_CSR", + "offset": "0x2C", + "fields": [ + [ + "PWRDWN", + 0, + 1 + ], + [ + "MGKPKTEN", + 1, + 1 + ], + [ + "RWKPKTEN", + 2, + 1 + ], + [ + "MGKPRCVD", + 5, + 1 + ], + [ + "RWKPRCVD", + 6, + 1 + ], + [ + "GLBLUCAST", + 9, + 1 + ], + [ + "RWKPTR", + 24, + 5 + ], + [ + "RWKFILTRST", + 31, + 1 + ] + ] + }, + { + "name": "EMACLPI_CRS", + "offset": "0x30", + "fields": [ + [ + "TLPIEN", + 0, + 1 + ], + [ + "TLPIEX", + 1, + 1 + ], + [ + "RLPIEN", + 2, + 1 + ], + [ + "RLPIEX", + 3, + 1 + ], + [ + "TLPIST", + 8, + 1 + ], + [ + "RLPIST", + 9, + 1 + ], + [ + "LPIEN", + 16, + 1 + ], + [ + "PLS", + 17, + 1 + ], + [ + "LPITXA", + 19, + 1 + ] + ] + }, + { + "name": "EMACLPITIMERSCONTROL", + "offset": "0x34", + "fields": [ + [ + "LPI_TW_TIMER", + 0, + 16 + ], + [ + "LPI_LS_TIMER", + 16, + 10 + ] + ] + }, + { + "name": "EMACINTS", + "offset": "0x38", + "fields": [ + [ + "PMTINTS", + 3, + 1 + ], + [ + "LPIIS", + 10, + 1 + ] + ] + }, + { + "name": "EMACINTMASK", + "offset": "0x3C", + "fields": [ + [ + "PMTINTMASK", + 3, + 1 + ], + [ + "LPIINTMASK", + 10, + 1 + ] + ] + }, + { + "name": "EMACADDR0HIGH", + "offset": "0x40", + "fields": [ + [ + "ADDRESS0_HI", + 0, + 16 + ], + [ + "ADDRESS_ENABLE0", + 31, + 1 + ] + ] + }, + { + "name": "EMACADDR0LOW", + "offset": "0x44" + }, + { + "name": "EMACADDR1HIGH", + "offset": "0x48", + "fields": [ + [ + "MAC_ADDRESS1_HI", + 0, + 16 + ], + [ + "MASK_BYTE_CONTROL", + 24, + 6 + ], + [ + "SOURCE_ADDRESS", + 30, + 1 + ], + [ + "ADDRESS_ENABLE1", + 31, + 1 + ] + ] + }, + { + "name": "EMACADDR1LOW", + "offset": "0x4C" + }, + { + "name": "EMACADDR2HIGH", + "offset": "0x50", + "fields": [ + [ + "MAC_ADDRESS2_HI", + 0, + 16 + ], + [ + "MASK_BYTE_CONTROL2", + 24, + 6 + ], + [ + "SOURCE_ADDRESS2", + 30, + 1 + ], + [ + "ADDRESS_ENABLE2", + 31, + 1 + ] + ] + }, + { + "name": "EMACADDR2LOW", + "offset": "0x54" + }, + { + "name": "EMACADDR3HIGH", + "offset": "0x58", + "fields": [ + [ + "MAC_ADDRESS3_HI", + 0, + 16 + ], + [ + "MASK_BYTE_CONTROL3", + 24, + 6 + ], + [ + "SOURCE_ADDRESS3", + 30, + 1 + ], + [ + "ADDRESS_ENABLE3", + 31, + 1 + ] + ] + }, + { + "name": "EMACADDR3LOW", + "offset": "0x5C" + }, + { + "name": "EMACADDR4HIGH", + "offset": "0x60", + "fields": [ + [ + "MAC_ADDRESS4_HI", + 0, + 16 + ], + [ + "MASK_BYTE_CONTROL4", + 24, + 6 + ], + [ + "SOURCE_ADDRESS4", + 30, + 1 + ], + [ + "ADDRESS_ENABLE4", + 31, + 1 + ] + ] + }, + { + "name": "EMACADDR4LOW", + "offset": "0x64" + }, + { + "name": "EMACADDR5HIGH", + "offset": "0x68", + "fields": [ + [ + "MAC_ADDRESS5_HI", + 0, + 16 + ], + [ + "MASK_BYTE_CONTROL5", + 24, + 6 + ], + [ + "SOURCE_ADDRESS5", + 30, + 1 + ], + [ + "ADDRESS_ENABLE5", + 31, + 1 + ] + ] + }, + { + "name": "EMACADDR5LOW", + "offset": "0x6C" + }, + { + "name": "EMACADDR6HIGH", + "offset": "0x70", + "fields": [ + [ + "MAC_ADDRESS6_HI", + 0, + 16 + ], + [ + "MASK_BYTE_CONTROL6", + 24, + 6 + ], + [ + "SOURCE_ADDRESS6", + 30, + 1 + ], + [ + "ADDRESS_ENABLE6", + 31, + 1 + ] + ] + }, + { + "name": "EMACADDR6LOW", + "offset": "0x74" + }, + { + "name": "EMACADDR7HIGH", + "offset": "0x78", + "fields": [ + [ + "MAC_ADDRESS7_HI", + 0, + 16 + ], + [ + "MASK_BYTE_CONTROL7", + 24, + 6 + ], + [ + "SOURCE_ADDRESS7", + 30, + 1 + ], + [ + "ADDRESS_ENABLE7", + 31, + 1 + ] + ] + }, + { + "name": "EMACADDR7LOW", + "offset": "0x7C" + }, + { + "name": "EMACCSTATUS", + "offset": "0xD8", + "fields": [ + [ + "LINK_MODE", + 0, + 1 + ], + [ + "LINK_SPEED", + 1, + 2 + ], + [ + "JABBER_TIMEOUT", + 4, + 1 + ] + ] + }, + { + "name": "EMACWDOGTO", + "offset": "0xDC", + "fields": [ + [ + "WDOGTO", + 0, + 14 + ], + [ + "PWDOGEN", + 16, + 1 + ] + ] + } + ] + }, + { + "name": "TWAI0", + "base": "0x3FF6B000", + "interrupts": [ + "TWAI0" + ], + "registers": [ + { + "name": "MODE", + "offset": "0x00", + "reset": "0x00000001", + "fields": [ + [ + "RESET_MODE", + 0, + 1 + ], + [ + "LISTEN_ONLY_MODE", + 1, + 1 + ], + [ + "SELF_TEST_MODE", + 2, + 1 + ], + [ + "RX_FILTER_MODE", + 3, + 1 + ] + ] + }, + { + "name": "CMD", + "offset": "0x04", + "fields": [ + [ + "TX_REQ", + 0, + 1, + "w" + ], + [ + "ABORT_TX", + 1, + 1, + "w" + ], + [ + "RELEASE_BUF", + 2, + 1, + "w" + ], + [ + "CLR_OVERRUN", + 3, + 1, + "w" + ], + [ + "SELF_RX_REQ", + 4, + 1, + "w" + ] + ] + }, + { + "name": "STATUS", + "offset": "0x08", + "fields": [ + [ + "RX_BUF_ST", + 0, + 1, + "r" + ], + [ + "OVERRUN_ST", + 1, + 1, + "r" + ], + [ + "TX_BUF_ST", + 2, + 1, + "r" + ], + [ + "TX_COMPLETE", + 3, + 1, + "r" + ], + [ + "RX_ST", + 4, + 1, + "r" + ], + [ + "TX_ST", + 5, + 1, + "r" + ], + [ + "ERR_ST", + 6, + 1, + "r" + ], + [ + "BUS_OFF_ST", + 7, + 1, + "r" + ], + [ + "MISS_ST", + 8, + 1, + "r" + ] + ] + }, + { + "name": "INT_RAW", + "offset": "0x0C", + "fields": [ + [ + "RX_INT_ST", + 0, + 1, + "r" + ], + [ + "TX_INT_ST", + 1, + 1, + "r" + ], + [ + "ERR_WARN_INT_ST", + 2, + 1, + "r" + ], + [ + "OVERRUN_INT_ST", + 3, + 1, + "r" + ], + [ + "ERR_PASSIVE_INT_ST", + 5, + 1, + "r" + ], + [ + "ARB_LOST_INT_ST", + 6, + 1, + "r" + ], + [ + "BUS_ERR_INT_ST", + 7, + 1, + "r" + ] + ] + }, + { + "name": "INT_ENA", + "offset": "0x10", + "fields": [ + [ + "RX_INT_ENA", + 0, + 1 + ], + [ + "TX_INT_ENA", + 1, + 1 + ], + [ + "ERR_WARN_INT_ENA", + 2, + 1 + ], + [ + "OVERRUN_INT_ENA", + 3, + 1 + ], + [ + "ERR_PASSIVE_INT_ENA", + 5, + 1 + ], + [ + "ARB_LOST_INT_ENA", + 6, + 1 + ], + [ + "BUS_ERR_INT_ENA", + 7, + 1 + ] + ] + }, + { + "name": "BUS_TIMING_0", + "offset": "0x18", + "fields": [ + [ + "BAUD_PRESC", + 0, + 6 + ], + [ + "SYNC_JUMP_WIDTH", + 6, + 2 + ] + ] + }, + { + "name": "BUS_TIMING_1", + "offset": "0x1C", + "fields": [ + [ + "TIME_SEG1", + 0, + 4 + ], + [ + "TIME_SEG2", + 4, + 3 + ], + [ + "TIME_SAMP", + 7, + 1 + ] + ] + }, + { + "name": "ARB_LOST_CAP", + "offset": "0x2C", + "fields": [ + [ + "ARB_LOST_CAP", + 0, + 5, + "r" + ] + ] + }, + { + "name": "ERR_CODE_CAP", + "offset": "0x30", + "fields": [ + [ + "ECC_SEGMENT", + 0, + 5, + "r" + ], + [ + "ECC_DIRECTION", + 5, + 1, + "r" + ], + [ + "ECC_TYPE", + 6, + 2, + "r" + ] + ] + }, + { + "name": "ERR_WARNING_LIMIT", + "offset": "0x34", + "reset": "0x00000060", + "fields": [ + [ + "ERR_WARNING_LIMIT", + 0, + 8 + ] + ] + }, + { + "name": "RX_ERR_CNT", + "offset": "0x38", + "fields": [ + [ + "RX_ERR_CNT", + 0, + 8 + ] + ] + }, + { + "name": "TX_ERR_CNT", + "offset": "0x3C", + "fields": [ + [ + "TX_ERR_CNT", + 0, + 8 + ] + ] + }, + { + "name": "DATA_0", + "offset": "0x40", + "fields": [ + [ + "TX_BYTE_0", + 0, + 8 + ] + ] + }, + { + "name": "DATA_1", + "offset": "0x44", + "fields": [ + [ + "TX_BYTE_1", + 0, + 8 + ] + ] + }, + { + "name": "DATA_2", + "offset": "0x48", + "fields": [ + [ + "TX_BYTE_2", + 0, + 8 + ] + ] + }, + { + "name": "DATA_3", + "offset": "0x4C", + "fields": [ + [ + "TX_BYTE_3", + 0, + 8 + ] + ] + }, + { + "name": "DATA_4", + "offset": "0x50", + "fields": [ + [ + "TX_BYTE_4", + 0, + 8 + ] + ] + }, + { + "name": "DATA_5", + "offset": "0x54", + "fields": [ + [ + "TX_BYTE_5", + 0, + 8 + ] + ] + }, + { + "name": "DATA_6", + "offset": "0x58", + "fields": [ + [ + "TX_BYTE_6", + 0, + 8 + ] + ] + }, + { + "name": "DATA_7", + "offset": "0x5C", + "fields": [ + [ + "TX_BYTE_7", + 0, + 8 + ] + ] + }, + { + "name": "DATA_8", + "offset": "0x60", + "fields": [ + [ + "TX_BYTE_8", + 0, + 8 + ] + ] + }, + { + "name": "DATA_9", + "offset": "0x64", + "fields": [ + [ + "TX_BYTE_9", + 0, + 8 + ] + ] + }, + { + "name": "DATA_10", + "offset": "0x68", + "fields": [ + [ + "TX_BYTE_10", + 0, + 8 + ] + ] + }, + { + "name": "DATA_11", + "offset": "0x6C", + "fields": [ + [ + "TX_BYTE_11", + 0, + 8 + ] + ] + }, + { + "name": "DATA_12", + "offset": "0x70", + "fields": [ + [ + "TX_BYTE_12", + 0, + 8 + ] + ] + }, + { + "name": "RX_MESSAGE_CNT", + "offset": "0x74", + "fields": [ + [ + "RX_MESSAGE_COUNTER", + 0, + 7, + "r" + ] + ] + }, + { + "name": "CLOCK_DIVIDER", + "offset": "0x7C", + "fields": [ + [ + "CD", + 0, + 8 + ], + [ + "CLOCK_OFF", + 8, + 1 + ] + ] + } + ] + }, + { + "name": "MCPWM1", + "base": "0x3FF6C000", + "derived_from": "MCPWM0" + }, + { + "name": "I2S1", + "base": "0x3FF6D000", + "interrupts": [ + "I2S1" + ], + "derived_from": "I2S0" + }, + { + "name": "UART2", + "base": "0x3FF6E000", + "interrupts": [ + "UART2" + ], + "derived_from": "UART0" + } + ] +} diff --git a/catalog/mcus/espressif/esp32/rom.bin b/catalog/mcus/espressif/esp32/rom.bin new file mode 100644 index 0000000000000000000000000000000000000000..0c4c8aa7cd093f5df5cab424962dad823e71ca0d GIT binary patch literal 458752 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

8c=)* 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≻gn!mse_b+BA#j$B<7F16SJy+}{zC*SmuGy4p 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 P{F4L!s), div = (esk_rf_read(k->p, SYSCLK_CONF, 4) & 0x3ffu) + 1u; + if (sel == 1 || sel == 3) { + u32 per = esk_esp32_dport_cpuperiod(k->s); + return per == 2 ? 240000000u : per == 1 ? 160000000u : 80000000u; + } + return (sel == 2 ? RC_FAST_HZ : ESP32_XTAL_HZ) / div; +} + +static u32 core_hz(struct esk_mach *m) +{ + struct e32_apbctrl *k = (struct e32_apbctrl *)m->clock_ctx; + if (!k) + return ESP32_XTAL_HZ; + k->hz = cpu_hz(k); + return k->hz; +} + +u32 esk_esp32_cpu_hz(struct esk_mach *m) +{ + return core_hz(m); +} + +u32 esk_esp32_apb_hz(struct esk_mach *m) +{ + u32 sel = esk_esp32_rtc_soc_clk_sel(m); + return sel == 1 || sel == 3 ? 80000000u : core_hz(m); +} + +static u32 ap_read(void *ctx, u32 off, int n) +{ + struct e32_apbctrl *k = (struct e32_apbctrl *)ctx; + return esk_rf_read(k->p, off, n); +} + +static void ap_write(void *ctx, u32 off, int n, u32 v) +{ + struct e32_apbctrl *k = (struct e32_apbctrl *)ctx; + esk_rf_write(k->p, off, n, v); + if (off == SYSCLK_CONF) + k->s->yield = 1; /* the runtime sees the core's new clock now */ +} + +static void ap_reset(void *ctx) +{ + struct e32_apbctrl *k = (struct e32_apbctrl *)ctx; + k->hz = ESP32_XTAL_HZ; +} + +const struct esk_dev_ops esk_esp32_apbctrl_ops = { + "esp32-apbctrl", ap_read, ap_write, ap_reset, 0, 0, +}; + +void *esk_esp32_apbctrl_create(struct esk_mach *s, struct esk_rf_periph *p) +{ + struct e32_apbctrl *k = (struct e32_apbctrl *)esk_mach_alloc(s, sizeof *k); + struct esk_rf_reg *date = esk_rf_reg_at(p, DATE); + if (!k) + return 0; + k->s = s; + k->p = p; + k->hz = ESP32_XTAL_HZ; + if (date) + esk_rf_set_reset(date, date->reset | 0x80000000u); + s->core_hz = core_hz; + s->clock_ctx = k; + return k; +} diff --git a/core/mcu/esp32/soc/dport.c b/core/mcu/esp32/soc/dport.c new file mode 100644 index 000000000..f72c1c48c --- /dev/null +++ b/core/mcu/esp32/soc/dport.c @@ -0,0 +1,346 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* dport.c -- the ESP32's DPORT registers (TRM v5 chapters 3, 4 and 5): + * + * - the APP_CPU: held in reset while APPCPU_CTRL_A's RESETTING is set + * (1 out of reset), its clock gated while APPCPU_CTRL_B's CLKGATE_EN + * is clear, stalled while APPCPU_CTRL_C's RUNSTALL is set; released, + * it starts at its reset vector, where the ROM reads APPCPU_CTRL_D's + * boot address; + * - the interrupt matrix (chapter 4): 69 peripheral sources, each mapped + * by its PRO_ and APP_ *_MAP register onto one of each CPU's 32 + * interrupts; an interrupt sees the OR of the sources mapped onto it, + * those numbers the CPU drives itself (its timers 6, 15 and 16, its + * software interrupts 7 and 29, profiling 11) none; PRO_ and + * APP_INTR_STATUS_0-2 read the sources' levels; CPU_INTR_FROM_CPU_0-3 + * drive sources 24-27; + * - the caches and their flash MMU (chapter 3.3.2): each CPU's table of + * 64 KB pages at 0x3FF10000 (PRO_CPU) and 0x3FF12000 (APP_CPU), an + * entry's bits [7:0] the flash page and bit 8 invalid; entries 0-63 + * map DROM0 (0x3F400000), 64-127 IRAM0 (0x40000000, of which + * 0x400D0000 up is the description's flash_irom window); the window + * reads the flash's bytes the PRO_CPU's entries name. A cache flush + * (PRO_ or APP_CACHE_CTRL's FLUSH_ENA) is done at once (FLUSH_DONE), + * and the caches' state (PRO_ and APP_DCACHE_DBUG0's CACHE_STATE) + * reads 1, idle, which ESP-IDF waits for before disabling them; + * pages whose flash the program has written since they were mapped + * show it after a flush; + * - CPU_PER_CONF's CPUPERIOD_SEL, which with RTC_CNTL's SOC_CLK_SEL and + * APB_CTRL's divider sets the CPU's clock (apbctrl.c). + * Inside, the caches hold the mapped pages whole rather than 32 KB of + * lines, and both CPUs read through the PRO_CPU's table (ESP-IDF keeps + * the two the same); a CPU's cache enable and the buses' mask bits + * (PRO_CACHE_CTRL1) do not stop accesses. + * Not yet: the cache's lock regions, PSRAM (DRAM1, which the module does + * not have), the boot remap (PRO_BOOT_REMAP_CTRL), the APB and AHB + * access permissions (DPORT_APB_MASK, AHBLITE_MPU_TABLE), the MMU's + * access-error interrupts, the trace memory and the CPU record. */ +#include + +#include "mcu/esp32/soc/esp32.h" +#include "mcu/xtensa/lx6.h" + + +#define APPCPU_CTRL_A 0x2Cu +#define APPCPU_CTRL_B 0x30u +#define APPCPU_CTRL_C 0x34u +#define CPU_PER_CONF 0x3Cu +#define PRO_CACHE_CTRL 0x40u +#define APP_CACHE_CTRL 0x58u +#define FROM_CPU_0 0xDCu +#define PRO_STATUS_0 0xECu +#define APP_STATUS_0 0xF8u +#define PRO_MAP 0x104u +#define APP_MAP 0x218u +#define PRO_DCACHE_DBUG0 0x3F0u +#define APP_DCACHE_DBUG0 0x418u +#define CACHE_FLUSH_ENA 0x10u +#define CACHE_FLUSH_DONE 0x20u + +#define MMU_BASE 0x3FF10000u +#define MMU_WORDS 2048 /* per CPU: 0x2000 bytes */ +#define MMU_INVALID 0x100u +#define PAGE 0x10000u +#define DROM 0x3F400000u +#define IROM 0x400D0000u +#define IROM_FIRST 77 /* 0x400D0000's entry */ +#define INTERNAL_INTS 0x20018840u /* 6, 7, 11, 15, 16, 29: the CPU's own */ + +struct e32_dport { + struct esk_mach *s; + struct esk_rf_periph *p; + u8 src[ESP32_NSOURCES]; + u32 lines[2]; /* each CPU's interrupt inputs as the matrix drives them */ + u32 mmu[2][MMU_WORDS]; + u8 stale[128]; + u8 *app_rom; /* the ROM as the APP_CPU sees it */ +}; + +static struct e32_dport *dport_of(struct esk_mach *m) +{ + return m->rf ? (struct e32_dport *)esk_rf_ctx_of(m->rf, &esk_esp32_dport_ops) : 0; +} + +static u32 reg(struct e32_dport *k, u32 off) +{ + return esk_rf_read(k->p, off, 4); +} + +static void hw(struct e32_dport *k, u32 off, u32 v) +{ + struct esk_rf_reg *r = esk_rf_reg_at(k->p, off); + if (r) + esk_rf_hw(r, v); +} + +/* ---- the interrupt matrix ---------------------------------------------- */ + +static void route(struct e32_dport *k) +{ + int cpu, i, n = esk_lx6_cores(k->s); + for (cpu = 0; cpu < 2; cpu++) { + u32 lines = 0, st[3] = { 0, 0, 0 }; + for (i = 0; i < ESP32_NSOURCES; i++) { + u32 map = reg(k, (cpu ? APP_MAP : PRO_MAP) + 4u * (u32)i) & 31u; + if (!(k->src[i] & (1u << cpu))) + continue; + st[i / 32] |= 1u << (i % 32); + if (!(INTERNAL_INTS & (1u << map))) + lines |= 1u << map; + } + for (i = 0; i < 3; i++) + hw(k, (cpu ? APP_STATUS_0 : PRO_STATUS_0) + 4u * (u32)i, st[i]); + if (cpu < n && lines != k->lines[cpu]) { + u32 ch = lines ^ k->lines[cpu]; + k->lines[cpu] = lines; + for (i = 0; i < 32; i++) + if (ch & (1u << i)) + esk_lx6_irq(k->s, cpu, i, (int)((lines >> i) & 1u)); + } + } +} + +void esk_esp32_source(struct esk_mach *m, int src, int level) +{ + struct e32_dport *k = dport_of(m); + u8 v = level ? 3u : 0u; + if (!k || src < 0 || src >= ESP32_NSOURCES || k->src[src] == v) + return; + k->src[src] = v; + route(k); +} + +void esk_esp32_source_cpu(struct esk_mach *m, int cpu, int src, int level) +{ + struct e32_dport *k = dport_of(m); + u8 v; + if (!k || src < 0 || src >= ESP32_NSOURCES || cpu < 0 || cpu > 1) + return; + v = (u8)(level ? k->src[src] | (1u << cpu) : k->src[src] & ~(1u << cpu)); + if (v == k->src[src]) + return; + k->src[src] = v; + route(k); +} + +/* ---- the flash MMU and the windows ------------------------------------- */ + +static void fill(struct e32_dport *k, int entry) +{ + struct esk_store *st = esk_mach_store(k->s, "flash"); + u32 base = entry < 64 ? DROM + (u32)entry * PAGE : IROM + (u32)(entry - IROM_FIRST) * PAGE; + struct esk_region *w; + u32 e = k->mmu[0][entry]; + u8 *dst; + if (entry >= 64 && entry < IROM_FIRST) + return; + w = esk_bus_region(&k->s->bus, base, PAGE); + if (!w || w->kind != ESK_MEM_WINDOW) + return; + dst = w->mem + (base - w->base); + if ((e & MMU_INVALID) || !st || st->size < PAGE) + memset(dst, 0, PAGE); + else + memcpy(dst, st->mem + ((e & 0xffu) * PAGE) % st->size, PAGE); + if (entry < 128) + k->stale[entry] = 0; + esk_bus_code_changed(&k->s->bus); +} + +void esk_esp32_flash_written(struct esk_mach *m, u32 addr, u32 len) +{ + struct e32_dport *k = dport_of(m); + int i; + if (!k || !len) + return; + for (i = 0; i < 128; i++) { + u32 at = (k->mmu[0][i] & 0xffu) * PAGE; + if (!(k->mmu[0][i] & MMU_INVALID) && addr < at + PAGE && at < addr + len) + k->stale[i] = 1; + } +} + +static void flush(struct e32_dport *k) +{ + int i; + for (i = 0; i < 128; i++) + if (k->stale[i]) + fill(k, i); +} + +static u32 mmu_read(void *ctx, u32 off, int n) +{ + struct e32_dport *k = (struct e32_dport *)ctx; + u32 cpu = off >= 0x2000u, i = (off & 0x1fffu) / 4u; + ES_UNUSED(n); + return k->mmu[cpu][i] >> (8 * (off & 3u)); +} + +static void mmu_write(void *ctx, u32 off, int n, u32 v) +{ + struct e32_dport *k = (struct e32_dport *)ctx; + u32 cpu = off >= 0x2000u, i = (off & 0x1fffu) / 4u; + if (n != 4) + return; + v &= 0x1ffu; + if (k->mmu[cpu][i] == v) + return; + k->mmu[cpu][i] = v; + if (cpu == 0 && i < 128) + fill(k, (int)i); +} + +static void mmu_reset(void *ctx) +{ + struct e32_dport *k = (struct e32_dport *)ctx; + int i, c; + for (c = 0; c < 2; c++) + for (i = 0; i < MMU_WORDS; i++) + k->mmu[c][i] = MMU_INVALID; + for (i = 0; i < 128; i++) + fill(k, i); +} + +static const struct esk_dev_ops mmu_ops = { + "esp32-mmu", mmu_read, mmu_write, mmu_reset, 0, 0, +}; + +/* ---- the APP_CPU ------------------------------------------------------- */ + +static void app_cpu(struct e32_dport *k) +{ + esk_lx6_core_stall(k->s, 1, 0, !(reg(k, APPCPU_CTRL_B) & 1u)); + esk_lx6_core_stall(k->s, 1, 1, (int)(reg(k, APPCPU_CTRL_C) & 1u)); + esk_lx6_core_reset(k->s, 1, (int)(reg(k, APPCPU_CTRL_A) & 1u)); +} + +/* ---- the APP_CPU's ROM -------------------------------------------------- */ + +/* The APP_CPU's ROM: the PRO_CPU's with every word that points into the + * PRO_CPU's ROM data (0x3FFE0010-0x3FFE3F20: its .data, .bss and stack) + * pointing into the APP_CPU's (0x3FFE3F20 on: +0x3F10), so that the ROM's + * routines keep each CPU's state apart (ESP-IDF's esp32.rom.ld names + * g_ticks_per_us_pro and _app, and ets_update_cpu_frequency_rom "updates + * g_ticks_per_us on the current CPU only"). Espressif's QEMU loads this + * view as a second image (esp32-v3-rom-app.bin, sha256 6d3240e7...8442): + * the one made here equals it byte for byte over its 455,722 bytes. */ +#define ROM_BASE 0x40000000u +#define ROM_SIZE 0x70000u +#define ROM1_DBUS 0x3FF90000u +#define PRO_DATA_LO 0x3FFE0010u +#define PRO_DATA_HI 0x3FFE3F20u +#define APP_DATA_SHIFT 0x3F10u + +static void app_rom(struct e32_dport *k) +{ + struct esk_region *r = esk_bus_region(&k->s->bus, ROM_BASE, ROM_SIZE); + u32 o; + if (k->app_rom || !r || esk_lx6_cores(k->s) < 2) + return; + k->app_rom = (u8 *)esk_mach_alloc(k->s, ROM_SIZE); + if (!k->app_rom) + return; + memcpy(k->app_rom, r->mem + (ROM_BASE - r->base), ROM_SIZE); + for (o = 0; o < ROM_SIZE; o += 4) { + u8 *p = k->app_rom + o; + u32 v = (u32)p[0] | (u32)p[1] << 8 | (u32)p[2] << 16 | (u32)p[3] << 24; + if (v >= PRO_DATA_LO && v <= PRO_DATA_HI) { + v += APP_DATA_SHIFT; + p[0] = (u8)v; + p[1] = (u8)(v >> 8); + p[2] = (u8)(v >> 16); + p[3] = (u8)(v >> 24); + } + } + (void)esk_lx6_view(k->s, 1, ROM_BASE, ROM_SIZE, k->app_rom); + (void)esk_lx6_view(k->s, 1, ROM1_DBUS, 0x10000u, k->app_rom + 0x60000u); +} + +/* ---- the registers ----------------------------------------------------- */ + +static u32 dp_read(void *ctx, u32 off, int n) +{ + struct e32_dport *k = (struct e32_dport *)ctx; + return esk_rf_read(k->p, off, n); +} + +static void dp_write(void *ctx, u32 off, int n, u32 v) +{ + struct e32_dport *k = (struct e32_dport *)ctx; + esk_rf_write(k->p, off, n, v); + if (off >= APPCPU_CTRL_A && off <= APPCPU_CTRL_C) { + app_cpu(k); + } else if (off == PRO_CACHE_CTRL || off == APP_CACHE_CTRL) { + u32 c = reg(k, off); + if (c & CACHE_FLUSH_ENA) { + flush(k); + c |= CACHE_FLUSH_DONE; + } else { + c &= ~CACHE_FLUSH_DONE; + } + hw(k, off, c); + } else if (off >= FROM_CPU_0 && off < FROM_CPU_0 + 16u) { + esk_esp32_source(k->s, 24 + (int)((off - FROM_CPU_0) / 4u), (int)(reg(k, off) & 1u)); + } else if (off >= PRO_MAP && off < APP_MAP + 4u * ESP32_NSOURCES) { + route(k); + } else if (off == CPU_PER_CONF) { + k->s->yield = 1; /* the CPU's clock may change (apbctrl.c) */ + } +} + +static void dp_reset(void *ctx) +{ + struct e32_dport *k = (struct e32_dport *)ctx; + app_rom(k); + memset(k->src, 0, sizeof k->src); + k->lines[0] = k->lines[1] = 0; + hw(k, PRO_CACHE_CTRL, reg(k, PRO_CACHE_CTRL) | CACHE_FLUSH_DONE); + hw(k, APP_CACHE_CTRL, reg(k, APP_CACHE_CTRL) | CACHE_FLUSH_DONE); + hw(k, PRO_DCACHE_DBUG0, 1u << 7); /* CACHE_STATE: idle */ + hw(k, APP_DCACHE_DBUG0, 1u << 7); + route(k); +} + +const struct esk_dev_ops esk_esp32_dport_ops = { + "esp32-dport", dp_read, dp_write, dp_reset, 0, 0, +}; + +void *esk_esp32_dport_create(struct esk_mach *s, struct esk_rf_periph *p) +{ + struct e32_dport *k = (struct e32_dport *)esk_mach_alloc(s, sizeof *k); + if (!k) + return 0; + k->s = s; + k->p = p; + if (esk_mach_add_dev(s, MMU_BASE, 2u * MMU_WORDS * 4u, &mmu_ops, k) != ES_OK || + esk_esp32_misc_add(s) || esk_esp32_regi2c_add(s)) + return 0; + mmu_reset(k); + return k; +} + +u32 esk_esp32_dport_cpuperiod(struct esk_mach *m) +{ + struct e32_dport *k = dport_of(m); + return k ? reg(k, CPU_PER_CONF) & 3u : 0; +} diff --git a/core/mcu/esp32/soc/efuse.c b/core/mcu/esp32/soc/efuse.c new file mode 100644 index 000000000..050574ec0 --- /dev/null +++ b/core/mcu/esp32/soc/efuse.c @@ -0,0 +1,261 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* efuse.c -- the ESP32's eFuse controller (TRM v5 chapter 20): four blocks + * of one-time-programmable bits (BLOCK0's seven words, BLOCK1-3's eight), + * read into the RDATA registers at a chip reset and again by a read + * command, programmed (bits only from 0 to 1) from the WDATA registers by + * a program command. + * + * - CMD's READ_CMD (bit 0) with CONF's OP_CODE 0x5AA5 loads every block + * into its RDATA registers; PGM_CMD (bit 1) with 0x5A5A ORs every + * block's WDATA into its bits. CMD reads the command until it is done + * (the ROM checks that it does: a CMD that reads 0 at once is a + * glitch, and it resets the chip), then 0, and INT_RAW's READ_DONE or + * PGM_DONE is set; another op code does nothing. The WDATA registers + * keep what is written until written again. + * - Read protection: BLOCK0's RD_DIS bits (16-19) make BLOCK1, BLOCK2 or + * BLOCK3 read 0 (the flash encryption key in BLOCK1 is still used by + * the hardware). Write protection: a block whose WR_DIS bit (7, 8, 9 + * for BLOCK1-3) is set is not programmed; BLOCK0's own fields are + * programmed whatever their WR_DIS bits say (not yet: per field). + * + * The chip's bits out of the factory, those of an ESP32-D0WD-V3 of chip + * revision v3.1 as esptool reads the owner's board: CHIP_PACKAGE 1 + * (D0WD), CHIP_VER_REV1 and CHIP_VER_REV2 set (with APB_CTRL's DATE bit + * 31, the major revision 3), WAFER_VERSION_MINOR 1; Coding Scheme None; + * an ADC reference voltage calibration (ADC_VREF, "Vref calibration in + * eFuse"); the MAC 24:0A:C4:45:6D:62, Espressif's OUI and a serial of the + * model's, with its CRC-8 (the ROM's esp_rom_efuse_mac_address_crc8), or + * the one a board's settings give its chip (settings.mac); every other + * bit 0 (no secure boot, no flash encryption, no keys). + * Inside, a read takes 1 us and a program 1 ms, whatever EFUSE_CLK's + * timing fields say (estimates: the TRM gives the timing parameters, not + * the operations' durations). + * Not yet: the 3/4 coding scheme's check, BLOCK0's per-field write + * protection. */ +#include + +#include "mcu/esp32/soc/esp32.h" + +#define OP_READ 0x5AA5u +#define OP_PGM 0x5A5Au +#define CONF 0xFCu +#define CMD 0x104u +#define INT_RAW 0x108u +#define INT_ST 0x10Cu +#define INT_ENA 0x110u +#define INT_CLR 0x114u + +/* each block's RDATA and WDATA offsets and words */ +static const struct { + u32 rd, wr; + int words; +} blocks[4] = { { 0x00, 0x1C, 7 }, { 0x38, 0x98, 8 }, { 0x58, 0xB8, 8 }, { 0x78, 0xD8, 8 } }; + +struct e32_efuse { + struct esk_mach *s; + struct esk_rf_periph *p; + u32 fuse[4][8]; /* the bits themselves */ + u32 raw; /* INT_RAW */ + int programmed; /* the bits differ from the factory's */ + u32 busy; /* the command in progress (1 read, 2 program), or 0 */ + u64 left; /* its cycles still to go */ + int clocked; +}; + +static void hw(struct esk_rf_periph *p, u32 off, u32 v) +{ + struct esk_rf_reg *r = esk_rf_reg_at(p, off); + if (r) + esk_rf_hw(r, v); +} + +static struct e32_efuse *efuse_of(struct esk_mach *m) +{ + return m->rf ? (struct e32_efuse *)esk_rf_ctx_of(m->rf, &esk_esp32_efuse_ops) : 0; +} + +/* CRC-8, polynomial x^8 + x^5 + x^4 + 1 reflected (0x8C), as the ROM's + * esp_rom_efuse_mac_address_crc8 computes it over the MAC */ +static u8 crc8(const u8 *p, int n) +{ + u8 crc = 0; + int i, b; + for (i = 0; i < n; i++) { + crc ^= p[i]; + for (b = 0; b < 8; b++) + crc = (u8)((crc & 1u) ? (crc >> 1) ^ 0x8Cu : crc >> 1); + } + return crc; +} + +static void factory(struct e32_efuse *k) +{ + static const u8 dflt[6] = { 0x24, 0x0A, 0xC4, 0x45, 0x6D, 0x62 }; + const u8 *mac = k->s->has_factory_mac ? k->s->factory_mac : dflt; + memset(k->fuse, 0, sizeof k->fuse); + /* MAC: byte 0 at bit 72, ..., byte 5 at bit 32; its CRC at bit 80 */ + k->fuse[0][1] = (u32)mac[5] | (u32)mac[4] << 8 | (u32)mac[3] << 16 | (u32)mac[2] << 24; + k->fuse[0][2] = (u32)mac[1] | (u32)mac[0] << 8 | (u32)crc8(mac, 6) << 16; + k->fuse[0][3] = 1u << 9 /* CHIP_PACKAGE 1: D0WD */ + | 1u << 15; /* CHIP_VER_REV1 */ + k->fuse[0][4] = 0x0Au << 8; /* ADC_VREF */ + k->fuse[0][5] = 1u << 20 /* CHIP_VER_REV2 */ + | 1u << 24; /* WAFER_VERSION_MINOR 1 */ +} + +/* the RDATA registers from the bits, with read protection */ +static void reload(struct e32_efuse *k) +{ + u32 rd_dis = (k->fuse[0][0] >> 16) & 15u; + int b, w; + for (b = 0; b < 4; b++) + for (w = 0; w < blocks[b].words; w++) { + u32 v = k->fuse[b][w]; + if (b > 0 && (rd_dis & (1u << (b - 1)))) + v = 0; + hw(k->p, blocks[b].rd + 4u * (u32)w, v); + } +} + +static void program(struct e32_efuse *k) +{ + u32 wr_dis = k->fuse[0][0] & 0xffffu; + int b, w; + for (b = 0; b < 4; b++) { + if (b > 0 && (wr_dis & (1u << (6 + b)))) + continue; + for (w = 0; w < blocks[b].words; w++) + k->fuse[b][w] |= esk_rf_read(k->p, blocks[b].wr + 4u * (u32)w, 4); + } + k->programmed = 1; +} + +static void irq(struct e32_efuse *k) +{ + u32 ena = esk_rf_read(k->p, INT_ENA, 4); + hw(k->p, INT_RAW, k->raw); + hw(k->p, INT_ST, k->raw & ena); + esk_esp32_source(k->s, esk_rf_irq(k->p, "EFUSE"), (k->raw & ena) != 0); +} + +static void start(struct e32_efuse *k, u32 cmd, u32 us) +{ + k->busy = cmd; + k->left = (u64)esk_mach_core_hz(k->s) * us / 1000000u; + if (!k->left) + k->left = 1; + hw(k->p, CMD, cmd); + esk_rf_clock(k->p, &k->clocked, 1); +} + +static void done(struct e32_efuse *k) +{ + if (k->busy == 1) { + reload(k); + k->raw |= 1u; + } else { + program(k); + k->raw |= 2u; + } + k->busy = 0; + hw(k->p, CMD, 0); + esk_rf_clock(k->p, &k->clocked, 0); + irq(k); +} + +static void ef_tick(void *ctx, u32 cycles) +{ + struct e32_efuse *k = (struct e32_efuse *)ctx; + if (!k->busy) + return; + if (k->left > cycles) { + k->left -= cycles; + return; + } + done(k); +} + +static int ef_next(void *ctx, u32 *cycles) +{ + struct e32_efuse *k = (struct e32_efuse *)ctx; + if (!k->busy) + return 0; + *cycles = k->left > 0xffffffffu ? 0xffffffffu : (u32)k->left; + return 1; +} + +static u32 ef_read(void *ctx, u32 off, int n) +{ + struct e32_efuse *k = (struct e32_efuse *)ctx; + return esk_rf_read(k->p, off, n); +} + +static void ef_write(void *ctx, u32 off, int n, u32 v) +{ + struct e32_efuse *k = (struct e32_efuse *)ctx; + esk_rf_write(k->p, off, n, v); + if (off == CMD) { + u32 op = esk_rf_read(k->p, CONF, 4) & 0xffffu; + if (!k->busy && (v & 1u) && op == OP_READ) + start(k, 1, 1); + else if (!k->busy && (v & 2u) && op == OP_PGM) + start(k, 2, 1000); + else + hw(k->p, CMD, k->busy); + } else if (off == INT_CLR) { + k->raw &= ~v; + hw(k->p, INT_CLR, 0); + irq(k); + } else if (off == INT_ENA) { + irq(k); + } +} + +static void ef_reset(void *ctx) +{ + struct e32_efuse *k = (struct e32_efuse *)ctx; + if (!k->programmed) + factory(k); /* the board's MAC is known by now */ + k->raw = 0; + k->busy = 0; + k->clocked = 0; + reload(k); +} + +const struct esk_dev_ops esk_esp32_efuse_ops = { + "esp32-efuse", ef_read, ef_write, ef_reset, ef_tick, ef_next, +}; + +void *esk_esp32_efuse_create(struct esk_mach *s, struct esk_rf_periph *p) +{ + struct e32_efuse *k = (struct e32_efuse *)esk_mach_alloc(s, sizeof *k); + if (!k) + return 0; + k->s = s; + k->p = p; + factory(k); + return k; +} + +int esk_esp32_efuse_flash_crypt(struct esk_mach *m) +{ + struct e32_efuse *k = efuse_of(m); + u32 cnt, ones = 0; + if (!k) + return 0; + cnt = (k->fuse[0][0] >> 20) & 0x7fu; + while (cnt) { + ones += cnt & 1u; + cnt >>= 1; + } + return (int)(ones & 1u); +} + +u32 esk_esp32_efuse_word(struct esk_mach *m, int block, int word) +{ + struct e32_efuse *k = efuse_of(m); + if (!k || block < 0 || block > 3 || word < 0 || word >= blocks[block].words) + return 0; + return k->fuse[block][word]; +} diff --git a/core/mcu/esp32/soc/esp32.h b/core/mcu/esp32/soc/esp32.h new file mode 100644 index 000000000..65cb9db5e --- /dev/null +++ b/core/mcu/esp32/soc/esp32.h @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* esp32.h -- the ESP32's peripheral models (ESP32 Technical Reference + * Manual v5), over the register file converted from Espressif's esp32.svd, + * for the framework's Xtensa LX6 cores (mcu/xtensa/lx6.c). + * + * efuse.c the eFuse controller and the chip's factory bits + * dport.c DPORT: the APP_CPU's reset, clock and stall, the CPU clock, + * the caches and their flash MMU, the interrupt matrix, the + * cross-CPU interrupts + * rtc.c RTC_CNTL: the reset causes, the RTC timer and watchdog, the + * software resets and stalls, the store registers + * uart.c UART0-2: FIFOs, frames at the baud rate, interrupts + * timg.c TIMG0 and TIMG1: T0, T1, the MWDT, RTC_SLOW_CLK's calibration + * gpio.c the GPIO matrix, the IO MUX and the strapping latch + * spi.c SPI0 and SPI1 in front of the flash, and the flash itself + * apbctrl.c APB_CTRL: the CPU's and APB's clock dividers + * sha.c the SHA accelerator + * misc.c the random number generator, the AHB view of the + * peripherals */ +#ifndef ESK_MCU_ESP32_SOC_H +#define ESK_MCU_ESP32_SOC_H + +#include "mcu/rf.h" + +/* the interrupt matrix's sources (TRM table 4.3-2) */ +#define ESP32_NSOURCES 69 + +/* a peripheral's interrupt source `src` (0-68) is at `level`, for both + * CPUs or (_cpu) one CPU's matrix (GPIO's PRO_CPU and APP_CPU interrupts) */ +void esk_esp32_source(struct esk_mach *m, int src, int level); +void esk_esp32_source_cpu(struct esk_mach *m, int cpu, int src, int level); + +extern const struct esk_dev_ops esk_esp32_efuse_ops; +void *esk_esp32_efuse_create(struct esk_mach *s, struct esk_rf_periph *p); +extern const struct esk_dev_ops esk_esp32_dport_ops; +void *esk_esp32_dport_create(struct esk_mach *s, struct esk_rf_periph *p); +extern const struct esk_dev_ops esk_esp32_rtc_ops; +void *esk_esp32_rtc_create(struct esk_mach *s, struct esk_rf_periph *p); +extern const struct esk_dev_ops esk_esp32_uart_ops; +void *esk_esp32_uart_create(struct esk_mach *s, struct esk_rf_periph *p); +extern const struct esk_dev_ops esk_esp32_timg_ops; +void *esk_esp32_timg_create(struct esk_mach *s, struct esk_rf_periph *p); +extern const struct esk_dev_ops esk_esp32_gpio_ops; +void *esk_esp32_gpio_create(struct esk_mach *s, struct esk_rf_periph *p); +extern const struct esk_dev_ops esk_esp32_iomux_ops; +void *esk_esp32_iomux_create(struct esk_mach *s, struct esk_rf_periph *p); +extern const struct esk_dev_ops esk_esp32_spi_ops; +void *esk_esp32_spi_create(struct esk_mach *s, struct esk_rf_periph *p); +extern const struct esk_dev_ops esk_esp32_apbctrl_ops; +void *esk_esp32_apbctrl_create(struct esk_mach *s, struct esk_rf_periph *p); +extern const struct esk_dev_ops esk_esp32_sha_ops; +void *esk_esp32_sha_create(struct esk_mach *s, struct esk_rf_periph *p); +/* the RNG and the AHB view of the peripherals (misc.c): 0, or -1 */ +int esk_esp32_misc_add(struct esk_mach *s); +/* the internal I2C master to the analog blocks (regi2c.c): 0, or -1 */ +int esk_esp32_regi2c_add(struct esk_mach *s); + +/* the chip's eFuse fields the hardware reads */ +int esk_esp32_efuse_flash_crypt(struct esk_mach *m); +u32 esk_esp32_efuse_word(struct esk_mach *m, int block, int word); + +/* the clocks: the CPU's (CPU_CLK), the APB's, the crystal's, in Hz */ +u32 esk_esp32_cpu_hz(struct esk_mach *m); +u32 esk_esp32_apb_hz(struct esk_mach *m); +#define ESP32_XTAL_HZ 40000000u +/* RTC_CNTL CLK_CONF's SOC_CLK_SEL, and RTC_SLOW_CLK in Hz */ +u32 esk_esp32_rtc_soc_clk_sel(struct esk_mach *m); +double esk_esp32_rtc_slow_hz(struct esk_mach *m); +/* DPORT CPU_PER_CONF's CPUPERIOD_SEL */ +u32 esk_esp32_dport_cpuperiod(struct esk_mach *m); + +/* a reset of TRM table 4.3-? `code` (1 POWERON, 3 SW_SYS, 12 SW_CPU, ...), + * of the whole chip (system) or one CPU */ +void esk_esp32_reset(struct esk_mach *m, int code); +void esk_esp32_cpu_reset(struct esk_mach *m, int core, int code); +/* the reset cause each CPU reports (RTC_CNTL RESET_STATE) */ +void esk_esp32_rtc_reset_cause(struct esk_mach *m, int core, int code); + +/* the flash's bytes from `addr` on, `len` of them, changed: the caches' + * pages over them are stale */ +void esk_esp32_flash_written(struct esk_mach *m, u32 addr, u32 len); + +/* the strapping pins' levels latched at reset (GPIO_STRAP) */ +u32 esk_esp32_strap(struct esk_mach *m); +/* where a UART's TXD (tx) or RXD is: 0 and the runtime's port and bit */ +int esk_esp32_uart_pin(struct esk_mach *m, const char *unit, int tx, int *port, int *bit); +/* the board drove a pin or let it go: the levels again */ +void esk_esp32_pins_changed(struct esk_mach *m); +/* a peripheral output signal of the GPIO matrix (TRM table 4-2) driven by + * its model: its level and enable */ +void esk_esp32_signal(struct esk_mach *m, int sig, int level, int oe); +/* a peripheral input signal's level as the matrix or the IO MUX gives it */ +int esk_esp32_signal_in(struct esk_mach *m, int sig); + +/* the flash chip (spi.c) */ +struct esk_esp32_nor; +struct esk_esp32_nor *esk_esp32_nor_of(struct esk_mach *m); +struct esk_esp32_nor *esk_esp32_nor_create(struct esk_mach *m); +void esk_esp32_nor_power_on(struct esk_esp32_nor *f); +int esk_esp32_nor_busy(struct esk_esp32_nor *f); +/* one transaction: `ntx` bytes the chip receives (the instruction first), + * then `nrx` bytes it answers with */ +void esk_esp32_nor_xfer(struct esk_esp32_nor *f, const u8 *tx, int ntx, u8 *rx, int nrx); +#endif diff --git a/core/mcu/esp32/soc/gpio.c b/core/mcu/esp32/soc/gpio.c new file mode 100644 index 000000000..cbd07fb86 --- /dev/null +++ b/core/mcu/esp32/soc/gpio.c @@ -0,0 +1,534 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* gpio.c -- the ESP32's GPIO matrix and IO MUX (TRM v5 chapter 4, + * "IO_MUX and GPIO Matrix"; ESP32 Series Datasheet's pin tables) on its 34 + * pads GPIO0-19, 21-23, 25-27 and 32-39 (the runtime's port n / 16, bit + * n % 16). The ESP32-C3's model (mcu/esp32c3/gpio.c) on the ESP32's + * registers. + * + * Each pad's IO_MUX register picks its function (MCU_SEL): function 2 is + * the GPIO matrix on every pad, and so is function 0 on the pads whose + * first function is their GPIO; GPIO1's function 0 is U0TXD and GPIO3's + * U0RXD, directly; GPIO6-11 (the module's flash) and the JTAG pads + * (GPIO12-15) leave the pad to their functions, which no model drives. + * - Through the matrix, GPIO_FUNCn_OUT_SEL_CFG's OUT_SEL names the pad's + * output: 256 is GPIO_OUT's (OUT1's) bit, with GPIO_ENABLE's + * (ENABLE1's) as its enable; a peripheral signal (TRM table 4-2) is + * that signal, its enable the peripheral's (1 for the UARTs' TXD, RTS + * and DTR) unless OEN_SEL takes GPIO_ENABLE's; INV_SEL and OEN_INV_SEL + * invert them. A peripheral's model sets its signals' levels and + * enables (esk_esp32_signal); a signal no model drives leaves the pad + * undriven. GPIO34-39 have no output driver. + * - GPIO_PINn's PAD_DRIVER makes the output open-drain; FUN_WPU and + * FUN_WPD pull the pad; FUN_IE lets its level into GPIO_IN (IN1), the + * interrupt and the peripherals' inputs. + * - The pad is its output where enabled; else what the board drives; + * else its pull; else 0. + * - A peripheral input (GPIO_FUNCy_IN_SEL_CFG) comes through the matrix + * from IN_SEL's pad (0x38: 1, 0x30: 0, IN_INV_SEL inverting) when SEL + * is set, else directly from the pad whose IO MUX function it is, else + * 0. + * - Interrupts (GPIO_PINn's INT_TYPE: 1 rising, 2 falling, 3 any edge, + * 4 low level, 5 high level; INT_ENA bit 0 the APP_CPU's, 1 its NMI's, + * 2 the PRO_CPU's, 3 its NMI's) set GPIO_STATUS (STATUS1; W1TS and + * W1TC set and clear it; a level sets it again while it lasts); + * PCPU_INT, PCPU_NMI_INT, ACPU_INT and ACPU_NMI_INT (and their *1) are + * the enabled ones; the matrix sources GPIO (22) and GPIO_NMI (23) + * reach each CPU with that CPU's own. + * - GPIO_STRAP latches at a chip reset the boot-mode pins (TRM 4.? and + * the datasheet's strapping table): MTDI (GPIO12) bit 5, GPIO0 bit 4, + * GPIO2 bit 3, GPIO4 bit 2, MTDO (GPIO15) bit 1, GPIO5 bit 0, each + * its reset pull's level when nothing drives it (GPIO0, MTDO, GPIO5 + * up; GPIO2, GPIO4, MTDI down): 0x13, SPI_FAST_FLASH_BOOT; GPIO0 held + * low gives 0x03, the download mode. It keeps them through every other + * reset. + * IO MUX's pads come out of reset as the datasheet's table has them (each + * pad's IE and pull), not with the SVD's one reset value for all. + * Not yet: the peripherals' signals but the UARTs' (a pad routed to one + * is not driven), the RTC IO MUX (RTC_IO's pad functions, its pulls, the + * hold), the input filter, sleep mode (the MCU_ pad bits, SLP_SEL), the + * drive strengths' effect, the pads' wake-up, the SDIO interrupt. */ +#include +#include + +#include "mcu/esp32/soc/esp32.h" + +#define NPIN 40 +#define SIG_U0TXD 14 +#define SIG_U0RTS 15 +#define SIG_U0DTR 16 +#define SIG_U1TXD 17 +#define SIG_U1RTS 18 +#define SIG_U2TXD 198 +#define SIG_U2RTS 199 +#define SIG_GPIO 256 +#define NSIG 256 + +#define MUX_FUN_WPD (1u << 7) +#define MUX_FUN_WPU (1u << 8) +#define MUX_FUN_IE (1u << 9) + +/* the pads that exist */ +#define PADS 0xFF0EFFFFFFull +/* the datasheet's reset column: 0 IE off; 1 IE; 2 IE and WPD; 3 IE and WPU */ +static const u8 reset_kind[NPIN] = { + 3, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 2, 1, 1, 3, 1, 1, 1, 1, + 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +/* function 0 is the pad's GPIO */ +static const u8 f0_is_gpio[NPIN] = { + 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 }; + +struct e32_gpio { + struct esk_mach *s; + struct esk_rf_periph *p, *mux; + struct esk_rf_reg *out, *out1, *enable, *enable1, *strap, *in, *in1, *status, *status1; + struct esk_rf_reg *pcpu, *pnmi, *acpu, *anmi, *pcpu1, *pnmi1, *acpu1, *anmi1; + struct esk_rf_reg *pin[NPIN], *fout[NPIN], *fin[NSIG], *iomux[NPIN]; + u32 latched; + int have, known, busy, irq[2], irq_nmi[2]; + u64 level, input; + esk_mach_pin cfg[NPIN]; + u8 sig_on[NSIG], sig_lv[NSIG], sig_oe[NSIG]; + int again; +}; + +static u32 reg(const struct esk_rf_reg *r) { return r ? (u32)r->value : 0; } + +static int pad(int n) { return n >= 0 && n < NPIN && ((PADS >> n) & 1u); } + +static struct e32_gpio *gpio_of(struct esk_mach *m) +{ + return m->rf ? (struct e32_gpio *)esk_rf_ctx_of(m->rf, &esk_esp32_gpio_ops) : 0; +} + +static u64 out64(const struct e32_gpio *k) { return (u64)reg(k->out) | (u64)reg(k->out1) << 32; } +static u64 en64(const struct e32_gpio *k) { return (u64)reg(k->enable) | (u64)reg(k->enable1) << 32; } + +/* the pad's IO MUX function: 2 the matrix (also function 0 of the pads + * whose function 0 is their GPIO), else its own */ +static u32 mux_sel(const struct e32_gpio *k, int n) +{ + u32 sel = (reg(k->iomux[n]) >> 12) & 7u; + return sel == 0 && f0_is_gpio[n] ? 2u : sel; +} + +static int signal_out(const struct e32_gpio *k, int sig, int *level, int *oe) +{ + if (sig >= 0 && sig < NSIG && k->sig_on[sig]) { + *level = k->sig_lv[sig]; + *oe = k->sig_oe[sig]; + return 1; + } + switch (sig) { + case SIG_U0TXD: case SIG_U1TXD: case SIG_U2TXD: /* idle high: the frames are the runtime's */ + case SIG_U0RTS: case SIG_U0DTR: case SIG_U1RTS: case SIG_U2RTS: + *level = 1; + *oe = 1; + return 1; + default: + return 0; + } +} + +static int pin_out(const struct e32_gpio *k, int n, int *level, int *oe, int *af) +{ + u32 sel = mux_sel(k, n); + *af = 0; + if (n >= 34) + return 0; /* input only */ + if (sel == 2) { + u32 f = reg(k->fout[n]), sig = f & 0x1ffu; + int lv = 0, en = 0; + if (sig == SIG_GPIO) { + lv = (int)(out64(k) >> n & 1u); + en = (int)(en64(k) >> n & 1u); + } else { + if (!signal_out(k, (int)sig, &lv, &en)) + return 0; + *af = 1; + if (f & (1u << 10)) + en = (int)(en64(k) >> n & 1u); + } + if (f & (1u << 9)) + lv = !lv; + if (f & (1u << 11)) + en = !en; + *level = lv; + *oe = en; + return 1; + } + if (sel == 0 && n == 1) { /* U0TXD */ + *level = 1; + *oe = 1; + *af = 1; + return 1; + } + return 0; +} + +static void irqs(struct e32_gpio *k, u64 rise, u64 fall) +{ + u64 st = (u64)reg(k->status) | (u64)reg(k->status1) << 32, ints[4] = { 0, 0, 0, 0 }; + int n, c; + for (n = 0; n < NPIN; n++) { + u32 pr, type, ena; + int in, hit; + if (!pad(n)) + continue; + pr = reg(k->pin[n]); + type = (pr >> 7) & 7u; + ena = (pr >> 13) & 0x1fu; + in = (int)(k->input >> n & 1u); + hit = (type == 1 && (rise >> n & 1u)) || (type == 2 && (fall >> n & 1u)) || + (type == 3 && ((rise | fall) >> n & 1u)) || (type == 4 && !in) || (type == 5 && in); + if (hit) + st |= 1ull << n; + if (type && (st >> n & 1u)) + for (c = 0; c < 4; c++) + if (ena & (1u << c)) + ints[c] |= 1ull << n; + } + esk_rf_hw(k->status, (u32)st); + esk_rf_hw(k->status1, (u32)(st >> 32) & 0xffu); + esk_rf_hw(k->acpu, (u32)ints[0]); + esk_rf_hw(k->anmi, (u32)ints[1]); + esk_rf_hw(k->pcpu, (u32)ints[2]); + esk_rf_hw(k->pnmi, (u32)ints[3]); + if (k->acpu1) { + esk_rf_hw(k->acpu1, (u32)(ints[0] >> 32)); + esk_rf_hw(k->anmi1, (u32)(ints[1] >> 32)); + esk_rf_hw(k->pcpu1, (u32)(ints[2] >> 32)); + esk_rf_hw(k->pnmi1, (u32)(ints[3] >> 32)); + } + for (c = 0; c < 2; c++) { + int cpu_int = ints[c ? 0 : 2] != 0, cpu_nmi = ints[c ? 1 : 3] != 0; + if (cpu_int != k->irq[c]) { + k->irq[c] = cpu_int; + esk_esp32_source_cpu(k->s, c, 22, cpu_int); + } + if (cpu_nmi != k->irq_nmi[c]) { + k->irq_nmi[c] = cpu_nmi; + esk_esp32_source_cpu(k->s, c, 23, cpu_nmi); + } + } +} + +/* the pads the matrix gives a UART's RX: the runtime reads their frames */ +static u64 uart_rx_pins(const struct e32_gpio *k) +{ + static const int sig[3] = { 14, 17, 198 }; + u64 m = 0; + int i; + for (i = 0; i < 3; i++) { + u32 f = reg(k->fin[sig[i]]), n = f & 0x3fu; + if ((f & (1u << 7)) && n < NPIN) + m |= 1ull << n; + } + return m; +} + +static void settle(struct e32_gpio *k) +{ + struct esk_mach *s = k->s; + u64 lvl = 0, in = 0, rise, fall, rx; + int n; + if (k->busy) { + k->again = 1; + return; + } + k->busy = 1; +again: + k->again = 0; + rx = uart_rx_pins(k); + for (n = 0; n < NPIN; n++) { + u32 mux; + int level = 0, oe = 0, af = 0, drive, ext = 0, pin, wpu, wpd, od; + esk_mach_pin o; + if (!pad(n)) + continue; + mux = reg(k->iomux[n]); + wpu = (mux & MUX_FUN_WPU) != 0; + wpd = (mux & MUX_FUN_WPD) != 0; + od = (reg(k->pin[n]) & (1u << 2)) != 0; + if (!pin_out(k, n, &level, &oe, &af)) + oe = 0; + drive = oe && !(od && level); + memset(&o, 0, sizeof o); + o.drive = oe ? level : -1; + o.open_drain = od; + o.pull = wpu && !wpd ? 1 : wpd && !wpu ? 2 : 0; + o.af = af || (mux_sel(k, n) == 0 && n == 3) /* U0RXD */ || (rx >> n & 1u); + o.analog = !(mux & MUX_FUN_IE) && !oe; + if (s->host.pin_cfg && (!k->known || memcmp(&k->cfg[n], &o, sizeof o) != 0)) + s->host.pin_cfg(s->host.ctx, n / 16, n % 16, &o); + k->cfg[n] = o; + if (drive) + pin = level; + else if (s->host.pin_in && s->host.pin_in(s->host.ctx, n / 16, n % 16, &ext)) + pin = ext != 0; + else + pin = wpu ? 1 : 0; + lvl |= (u64)pin << n; + if (mux & MUX_FUN_IE) + in |= (u64)pin << n; + } + rise = k->known ? in & ~k->input : 0; + fall = k->known ? ~in & k->input : 0; + k->level = lvl; + k->input = in; + k->known = 1; + esk_rf_hw(k->in, (u32)in); + esk_rf_hw(k->in1, (u32)(in >> 32) & 0xffu); + irqs(k, rise, fall); + if (k->again) + goto again; + k->busy = 0; +} + +void esk_esp32_signal(struct esk_mach *m, int sig, int level, int oe) +{ + struct e32_gpio *k = gpio_of(m); + u8 lv = (u8)(level != 0), en = (u8)(oe != 0); + if (!k || sig < 0 || sig >= NSIG) + return; + if (k->sig_on[sig] && k->sig_lv[sig] == lv && k->sig_oe[sig] == en) + return; + k->sig_on[sig] = 1; + k->sig_lv[sig] = lv; + k->sig_oe[sig] = en; + if (k->known) + settle(k); +} + +int esk_esp32_signal_in(struct esk_mach *m, int sig) +{ + struct e32_gpio *k = gpio_of(m); + u32 f; + if (!k || sig < 0 || sig >= NSIG) + return 0; + f = reg(k->fin[sig]); + if (f & (1u << 7)) { + u32 n = f & 0x3fu; + int v = n == 0x38u ? 1 : n == 0x30u ? 0 : n < NPIN ? (int)(k->input >> n & 1u) : 0; + return (f & (1u << 6)) ? !v : v; + } + if (sig == 14 && mux_sel(k, 3) == 0) /* U0RXD */ + return (int)(k->input >> 3 & 1u); + return 0; +} + +void esk_esp32_pins_changed(struct esk_mach *m) +{ + struct e32_gpio *k = gpio_of(m); + if (k) + settle(k); +} + +int esk_esp32_uart_pin(struct esk_mach *m, const char *unit, int tx, int *port, int *bit) +{ + static const int txs[3] = { SIG_U0TXD, SIG_U1TXD, SIG_U2TXD }, rxs[3] = { 14, 17, 198 }; + struct e32_gpio *k = gpio_of(m); + int u, n; + if (!k || strncmp(unit, "UART", 4) || unit[4] < '0' || unit[4] > '2' || unit[5]) + return -1; + u = unit[4] - '0'; + if (tx) { + for (n = 0; n < 34; n++) { + u32 sel; + if (!pad(n)) + continue; + sel = mux_sel(k, n); + if ((sel == 2 && (int)(reg(k->fout[n]) & 0x1ffu) == txs[u]) || (u == 0 && sel == 0 && n == 1)) { + *port = n / 16; + *bit = n % 16; + return 0; + } + } + return -1; + } + { + u32 f = reg(k->fin[rxs[u]]); + if (f & (1u << 7)) { + n = (int)(f & 0x3fu); + if (!pad(n)) + return -1; + } else if (u == 0 && mux_sel(k, 3) == 0) { + n = 3; + } else { + return -1; + } + *port = n / 16; + *bit = n % 16; + return 0; + } +} + +u32 esk_esp32_strap(struct esk_mach *m) +{ + struct e32_gpio *k = gpio_of(m); + return k ? k->latched : 0x13u; +} + +/* ---- the GPIO block ---------------------------------------------------- */ + +static u32 gp_read(void *ctx, u32 off, int n) +{ + struct e32_gpio *k = (struct e32_gpio *)ctx; + return esk_rf_read(k->p, off, n); +} + +static void set_clr(struct esk_rf_reg *r, int set, u32 v, u32 mask) +{ + esk_rf_hw(r, set ? reg(r) | (v & mask) : reg(r) & ~v); +} + +static void gp_write(void *ctx, u32 off, int n, u32 v) +{ + struct e32_gpio *k = (struct e32_gpio *)ctx; + const u32 lo = (u32)PADS, hi = (u32)(PADS >> 32) & 0xffu; + switch (off) { + case 0x38: case 0x3C: case 0x40: case 0x60: case 0x64: case 0x68: case 0x6C: + return; /* read-only */ + case 0x08: case 0x0C: set_clr(k->out, off == 0x08, v, lo); break; + case 0x14: case 0x18: set_clr(k->out1, off == 0x14, v, hi); break; + case 0x24: case 0x28: set_clr(k->enable, off == 0x24, v, lo); break; + case 0x30: case 0x34: set_clr(k->enable1, off == 0x30, v, hi); break; + case 0x48: case 0x4C: set_clr(k->status, off == 0x48, v, lo); break; + case 0x54: case 0x58: set_clr(k->status1, off == 0x54, v, hi); break; + default: + esk_rf_write(k->p, off, n, v); + } + settle(k); +} + +static int pin_level(struct e32_gpio *k, int gpio, int dflt) +{ + int level = dflt; + if (k->s->host.pin_in && k->s->host.pin_in(k->s->host.ctx, gpio / 16, gpio % 16, &level)) + return level != 0; + return dflt; +} + +static void gp_reset(void *ctx) +{ + struct e32_gpio *k = (struct e32_gpio *)ctx; + int c; + if (!k->have || k->s->reset_cause == ESK_RESET_POWER || k->s->reset_cause == ESK_RESET_PIN) { + k->latched = (u32)pin_level(k, 12, 0) << 5 | (u32)pin_level(k, 0, 1) << 4 | + (u32)pin_level(k, 2, 0) << 3 | (u32)pin_level(k, 4, 0) << 2 | + (u32)pin_level(k, 15, 1) << 1 | (u32)pin_level(k, 5, 1); + k->have = 1; + } + esk_rf_hw(k->strap, k->latched); + for (c = 0; c < 2; c++) { + k->irq[c] = k->irq_nmi[c] = 0; + esk_esp32_source_cpu(k->s, c, 22, 0); + esk_esp32_source_cpu(k->s, c, 23, 0); + } + k->known = 0; + settle(k); +} + +const struct esk_dev_ops esk_esp32_gpio_ops = { + "esp32-gpio", gp_read, gp_write, gp_reset, 0, 0, +}; + +/* ---- IO MUX ------------------------------------------------------------ */ + +static u32 mx_read(void *ctx, u32 off, int n) +{ + struct esk_rf_periph *p = (struct esk_rf_periph *)ctx; + return esk_rf_read(p, off, n); +} + +static void mx_write(void *ctx, u32 off, int n, u32 v) +{ + struct esk_rf_periph *p = (struct esk_rf_periph *)ctx; + esk_rf_write(p, off, n, v); + esk_esp32_pins_changed(p->sim); +} + +static void mx_reset(void *ctx) +{ + struct esk_rf_periph *p = (struct esk_rf_periph *)ctx; + char name[16]; + int i; + for (i = 0; i < NPIN; i++) { + struct esk_rf_reg *r; + u32 v = 2u << 10; /* FUN_DRV 2 */ + snprintf(name, sizeof name, "GPIO%d", i); + r = esk_rf_reg(p, name); + if (!r || !pad(i)) + continue; + switch (reset_kind[i]) { + case 1: v |= MUX_FUN_IE; break; + case 2: v |= MUX_FUN_IE | MUX_FUN_WPD; break; + case 3: v |= MUX_FUN_IE | MUX_FUN_WPU; break; + default: break; + } + esk_rf_hw(r, v); + } + esk_esp32_pins_changed(p->sim); +} + +const struct esk_dev_ops esk_esp32_iomux_ops = { + "esp32-iomux", mx_read, mx_write, mx_reset, 0, 0, +}; + +void *esk_esp32_iomux_create(struct esk_mach *s, struct esk_rf_periph *p) +{ + ES_UNUSED(s); + return esk_rf_reg(p, "GPIO39") ? p : 0; +} + +void *esk_esp32_gpio_create(struct esk_mach *s, struct esk_rf_periph *p) +{ + struct e32_gpio *k = (struct e32_gpio *)esk_mach_alloc(s, sizeof *k); + char name[32]; + int i; + if (!k) + return 0; + k->s = s; + k->p = p; + k->mux = esk_rf_periph(s->rf, "IO_MUX"); + k->out = esk_rf_reg(p, "OUT"); + k->out1 = esk_rf_reg(p, "OUT1"); + k->enable = esk_rf_reg(p, "ENABLE"); + k->enable1 = esk_rf_reg(p, "ENABLE1"); + k->strap = esk_rf_reg(p, "STRAP"); + k->in = esk_rf_reg(p, "IN"); + k->in1 = esk_rf_reg(p, "IN1"); + k->status = esk_rf_reg(p, "STATUS"); + k->status1 = esk_rf_reg(p, "STATUS1"); + k->acpu = esk_rf_reg(p, "ACPU_INT"); + k->anmi = esk_rf_reg(p, "ACPU_NMI_INT"); + k->pcpu = esk_rf_reg(p, "PCPU_INT"); + k->pnmi = esk_rf_reg(p, "PCPU_NMI_INT"); + k->acpu1 = esk_rf_reg(p, "ACPU_INT1"); + k->anmi1 = esk_rf_reg(p, "ACPU_NMI_INT1"); + k->pcpu1 = esk_rf_reg(p, "PCPU_INT1"); + k->pnmi1 = esk_rf_reg(p, "PCPU_NMI_INT1"); + if (!k->mux || !k->out || !k->out1 || !k->enable || !k->enable1 || !k->strap || !k->in || !k->in1 || + !k->status || !k->status1 || !k->acpu || !k->anmi || !k->pcpu || !k->pnmi) + return 0; + for (i = 0; i < NPIN; i++) { + if (!pad(i)) + continue; + snprintf(name, sizeof name, "PIN%d", i); + k->pin[i] = esk_rf_reg(p, name); + snprintf(name, sizeof name, "FUNC%d_OUT_SEL_CFG", i); + k->fout[i] = esk_rf_reg(p, name); + snprintf(name, sizeof name, "GPIO%d", i); + k->iomux[i] = esk_rf_reg(k->mux, name); + if (!k->pin[i] || !k->fout[i] || !k->iomux[i]) + return 0; + } + for (i = 0; i < NSIG; i++) { + snprintf(name, sizeof name, "FUNC%d_IN_SEL_CFG", i); + k->fin[i] = esk_rf_reg(p, name); + } + s->pins_changed = esk_esp32_pins_changed; + s->uart_pin = esk_esp32_uart_pin; + return k; +} diff --git a/core/mcu/esp32/soc/misc.c b/core/mcu/esp32/soc/misc.c new file mode 100644 index 000000000..18cd14be0 --- /dev/null +++ b/core/mcu/esp32/soc/misc.c @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* misc.c -- what the ESP32 answers outside its register description: + * + * - the random number generator's RNG_DATA (TRM v5 chapter 26) at + * 0x3FF75144: every read a new 32-bit number. The chip's numbers come + * from the noise of its RF and SAR ADC circuits; the model's from a + * generator (xorshift64* seeded by the chip's MAC), the same sequence + * every run so a run replays, another for each chip; + * - the peripherals' AHB view (TRM 3.1.4): 0x60000000-0x6003FFFF reaches + * the same registers as 0x3FF40000-0x3FF7FFFF (ESP-IDF reads UART FIFOs + * there). Inside, the access is passed to the bus at the DPORT address. */ +#include "mcu/esp32/soc/esp32.h" + +#define RNG_DATA 0x3FF75144u +#define AHB_BASE 0x60000000u +#define AHB_SIZE 0x40000u +#define DPORT_VIEW 0x3FF40000u + +struct e32_rng { + struct esk_mach *s; + u64 state; + int seeded; +}; + +static u32 rng_next(struct e32_rng *k) +{ + if (!k->seeded) { + int i; + k->seeded = 1; + if (k->s->has_factory_mac) + for (i = 0; i < 6; i++) + k->state = (k->state ^ k->s->factory_mac[i]) * 0x100000001B3ull; + if (!k->state) + k->state = 1; + } + k->state ^= k->state >> 12; + k->state ^= k->state << 25; + k->state ^= k->state >> 27; + return (u32)((k->state * 0x2545F4914F6CDD1Dull) >> 32); +} + +static u32 rng_read(void *ctx, u32 off, int n) +{ + ES_UNUSED(n); + return rng_next((struct e32_rng *)ctx) >> (8 * (off & 3u)); +} + +static void rng_write(void *ctx, u32 off, int n, u32 v) +{ + ES_UNUSED(ctx); + ES_UNUSED(off); + ES_UNUSED(n); + ES_UNUSED(v); +} + +const struct esk_dev_ops esk_esp32_rng_ops = { + "esp32-rng", rng_read, rng_write, 0, 0, 0, +}; + +static u32 ahb_read(void *ctx, u32 off, int n) +{ + struct esk_mach *s = (struct esk_mach *)ctx; + u32 v = 0; + if (esk_bus_read(&s->bus, DPORT_VIEW + off, n, &v)) + esk_bus_fault(&s->bus); + return v; +} + +static void ahb_write(void *ctx, u32 off, int n, u32 v) +{ + struct esk_mach *s = (struct esk_mach *)ctx; + if (esk_bus_write(&s->bus, DPORT_VIEW + off, n, v)) + esk_bus_fault(&s->bus); +} + +static const struct esk_dev_ops ahb_ops = { + "esp32-ahb", ahb_read, ahb_write, 0, 0, 0, +}; + +/* the RNG and the AHB view: 0, or -1 */ +int esk_esp32_misc_add(struct esk_mach *s) +{ + struct e32_rng *k = (struct e32_rng *)esk_mach_alloc(s, sizeof *k); + if (!k) + return -1; + k->s = s; + k->state = 0x456D6253696D0032ull; + if (esk_mach_add_dev(s, RNG_DATA, 4, &esk_esp32_rng_ops, k) != ES_OK || + esk_mach_add_dev(s, AHB_BASE, AHB_SIZE, &ahb_ops, s) != ES_OK) + return -1; + return 0; +} diff --git a/core/mcu/esp32/soc/nor.c b/core/mcu/esp32/soc/nor.c new file mode 100644 index 000000000..dee39cc21 --- /dev/null +++ b/core/mcu/esp32/soc/nor.c @@ -0,0 +1,365 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* nor.c -- the module's 4 MB SPI flash: a Giantec GT25Q32A-U (datasheet + * V2.5, section 9; JEDEC ID C4 60 16, the ID esptool reads on the owner's + * ESP32 board, docs/engineering/esp32-giantec-profile.md), what SPI0 and + * SPI1 (spi.c) see of it. Its bytes are the description's `flash` store, + * which keeps them through every reset. The instruction set is the + * ESP32-C3's XMC model's (mcu/esp32c3/nor.c), which the GT25Q32A shares: + * - Write Enable (06h), Volatile SR Write Enable (50h), Write Disable + * (04h); Read Status Register-1, -2, -3 (05h, 35h, 15h); Write Status + * Register-1 (01h, and -2 after it), -2 (31h), -3 (11h); + * - Read Data (03h), Fast Read (0Bh), Dual Output (3Bh), Quad Output + * (6Bh), Dual I/O (BBh), Quad I/O (EBh), Word Read Quad I/O (E7h): the + * quad ones need QE (S9); + * - Page Program (02h), Quad Page Program (32h), Sector Erase (20h), + * Block Erase (52h, D8h), Chip Erase (C7h, 60h), each needing WEL; + * - the three Security Registers (44h, 42h, 48h), Suspend (75h) and + * Resume (7Ah), Power-down (B9h) and Release (ABh: device ID 15h), + * Manufacturer/Device ID (90h: C4h 15h), JEDEC ID (9Fh: C4 60 16), + * Unique ID (4Bh), Enable Reset and Reset (66h, 99h). + * Busy times are the datasheet's maximums as the owner's profile records + * them (a one-byte program 150 us, a page 1.5 ms, a 4, 32 or 64 KB erase + * 8 ms, the chip 16 ms; a status write taken as an erase's), counted on the + * machine's clock. It ships with QE clear: a QIO image's bootloader sets + * it (bootloader_enable_qio_mode), as on the part. + * Not yet: QPI mode, burst wrap, the continuous read mode, SFDP (5Ah + * answers FFh), the block protection bits (kept, protecting nothing). */ +#include + +#include "mcu/esp32/soc/esp32.h" + +#define PAGE 256u +#define NOR_DEVICE_ID 0x15u +#define T_W_PS 8000000000ull /* 8 ms */ +#define T_BP_PS 150000000ull /* 150 us: a one-byte program */ +#define T_PP_PS 1500000000ull /* 1.5 ms */ +#define T_SE_PS 8000000000ull /* 8 ms */ +#define T_BE1_PS 8000000000ull /* 8 ms */ +#define T_BE2_PS 8000000000ull /* 8 ms */ +#define T_CE_PS 16000000000ull /* 16 ms */ + +#define SR1_BUSY 0x01u +#define SR1_WEL 0x02u +#define SR2_QE 0x02u +#define SR2_LB_SHIFT 3 +#define SR2_SUS 0x80u + +static const u8 unique_id[8] = { 0x45, 0x6d, 0x62, 0x53, 0x69, 0x6d, 0x00, 0x03 }; + +struct esk_esp32_nor { + struct esk_mach *m; + struct esk_store *st; + u8 sr[3]; /* the status registers as read */ + u8 sr_nv[3]; /* their non-volatile values */ + int vwe; /* 50h: the next status write is volatile */ + int pd, udpd; /* power-down, ultra-deep power-down */ + int reset_en; /* 66h was the last instruction */ + u64 now_ps, last_cycles; + u64 busy_end; /* the self-timed operation ends then (ps) */ + u64 sus_left; /* a suspended operation's remaining time */ + u8 sec[3][256]; +}; + +static u64 now_ps(struct esk_esp32_nor *f) +{ + u32 hz = esk_mach_core_hz(f->m); + u64 dc = f->m->cycles - f->last_cycles; + f->last_cycles = f->m->cycles; + if (hz) + f->now_ps += (u64)((double)dc * 1e12 / (double)hz); + return f->now_ps; +} + +static void sr_clear(struct esk_esp32_nor *f, int k, u32 bits) +{ + f->sr[k] = (u8)(f->sr[k] & ~bits); +} +static void sr_set(struct esk_esp32_nor *f, int k, u32 bits) +{ + f->sr[k] = (u8)(f->sr[k] | bits); +} + +static int busy(struct esk_esp32_nor *f) +{ + if ((f->sr[0] & SR1_BUSY) && now_ps(f) >= f->busy_end) { + sr_clear(f, 0, SR1_BUSY | SR1_WEL); + } + return (f->sr[0] & SR1_BUSY) != 0; +} + +static void start(struct esk_esp32_nor *f, u64 ps) +{ + sr_set(f, 0, SR1_BUSY); + f->busy_end = now_ps(f) + ps; +} + +static u32 addr24(const u8 *tx, int n) +{ + u32 a = 0; + int i; + for (i = 1; i <= 3; i++) + a = a << 8 | (i < n ? tx[i] : 0xffu); + return a; +} + +static void program(struct esk_esp32_nor *f, u32 a, const u8 *d, int n) +{ + u32 page = a & ~(PAGE - 1u), off = a & (PAGE - 1u); + int i; + for (i = 0; i < n; i++) { + u32 x = (page + ((off + (u32)i) & (PAGE - 1u))) % f->st->size; + f->st->mem[x] = (u8)(f->st->mem[x] & d[i]); + } + esk_esp32_flash_written(f->m, page % f->st->size, PAGE); +} + +static void erase(struct esk_esp32_nor *f, u32 a, u32 size) +{ + u32 base = (a & ~(size - 1u)) % f->st->size; + memset(f->st->mem + base, 0xff, size); + esk_esp32_flash_written(f->m, base, size); +} + +/* the security register an address names (0x1000, 0x2000, 0x3000), or -1 */ +static int sec_reg(u32 a) +{ + u32 r = a >> 12; + return (a >> 16) == 0 && (a & 0xf00u) == 0 && r >= 1 && r <= 3 ? (int)r - 1 : -1; +} + +static void write_sr(struct esk_esp32_nor *f, int k, u8 v) +{ + static const u8 writable[3] = { 0xfc, 0x7b, 0xe4 }; + u8 w = writable[k]; + if (k == 1) { + /* LB3-1 are one-time programmable: once set they stay */ + v = (u8)(v | (f->sr[1] & (7u << SR2_LB_SHIFT))); + } + f->sr[k] = (u8)((f->sr[k] & ~w) | (v & w)); + if (!f->vwe) + f->sr_nv[k] = (u8)((f->sr_nv[k] & ~w) | (v & w)); +} + +static void reset_device(struct esk_esp32_nor *f) +{ + memcpy(f->sr, f->sr_nv, sizeof f->sr); + sr_clear(f, 0, SR1_BUSY | SR1_WEL); + sr_clear(f, 1, SR2_SUS); + f->sus_left = 0; + f->vwe = 0; +} + +static int is_read_status(u8 op) +{ + return op == 0x05 || op == 0x35 || op == 0x15; +} + +void esk_esp32_nor_xfer(struct esk_esp32_nor *f, const u8 *tx, int ntx, u8 *rx, int nrx) +{ + u8 op; + u32 a; + int i, qe, sr; + memset(rx, 0xff, (size_t)nrx); + if (f->udpd) { + f->udpd = 0; /* any /CS pulse brings it out (7.2.40) */ + return; + } + if (ntx < 1) + return; + op = tx[0]; + if (f->pd) { + if (op == 0x79 && !busy(f)) { + f->udpd = 1; + return; + } + if (op != 0xab) + return; + } + if (busy(f) && !is_read_status(op) && op != 0x75 && op != 0x66 && op != 0x99) + return; + if (op != 0x99) + f->reset_en = op == 0x66; + qe = (f->sr[1] & SR2_QE) != 0; + a = addr24(tx, ntx); + switch (op) { + case 0x06: /* write enable */ + sr_set(f, 0, SR1_WEL); + f->vwe = 0; + break; + case 0x50: + f->vwe = 1; + break; + case 0x04: + sr_clear(f, 0, SR1_WEL); + f->vwe = 0; + break; + case 0x05: + case 0x35: + case 0x15: + sr = op == 0x05 ? 0 : op == 0x35 ? 1 : 2; + busy(f); + for (i = 0; i < nrx; i++) + rx[i] = f->sr[sr]; + break; + case 0x01: + case 0x31: + case 0x11: + if (!f->vwe && !(f->sr[0] & SR1_WEL)) + break; + if (ntx < 2) + break; + sr = op == 0x01 ? 0 : op == 0x31 ? 1 : 2; + write_sr(f, sr, tx[1]); + if (op == 0x01 && ntx >= 3) + write_sr(f, 1, tx[2]); + if (f->vwe) + f->vwe = 0; + else + start(f, T_W_PS); /* WEL clears as the cycle ends */ + break; + case 0x6b: + case 0xeb: + case 0xe7: + if (!qe) + break; + /* fall through */ + case 0x03: + case 0x0b: + case 0x3b: + case 0xbb: + for (i = 0; i < nrx; i++) + rx[i] = f->st->mem[(a + (u32)i) % f->st->size]; + break; + case 0x32: + if (!qe) + break; + /* fall through */ + case 0x02: + if (!(f->sr[0] & SR1_WEL) || ntx < 5) + break; + program(f, a, tx + 4, ntx - 4); + start(f, ntx - 4 == 1 ? T_BP_PS : T_PP_PS); + break; + case 0x20: + case 0x52: + case 0xd8: + if (!(f->sr[0] & SR1_WEL) || ntx < 4) + break; + erase(f, a, op == 0x20 ? 4096u : op == 0x52 ? 32768u : 65536u); + start(f, op == 0x20 ? T_SE_PS : op == 0x52 ? T_BE1_PS : T_BE2_PS); + break; + case 0xc7: + case 0x60: + if (!(f->sr[0] & SR1_WEL)) + break; + erase(f, 0, f->st->size); + start(f, T_CE_PS); + break; + case 0x44: + case 0x42: + sr = sec_reg(a); + if (!(f->sr[0] & SR1_WEL) || ntx < 4 || sr < 0 || ((f->sr[1] >> (SR2_LB_SHIFT + sr)) & 1)) { + sr_clear(f, 0, SR1_WEL); + break; + } + if (op == 0x44) { + memset(f->sec[sr], 0xff, 256); + start(f, T_SE_PS); + } else { + for (i = 4; i < ntx; i++) + f->sec[sr][(a + (u32)(i - 4)) & 0xffu] = + (u8)(f->sec[sr][(a + (u32)(i - 4)) & 0xffu] & tx[i]); + start(f, T_PP_PS); + } + break; + case 0x48: + sr = sec_reg(a); + for (i = 0; sr >= 0 && i < nrx; i++) + rx[i] = (u8)((a & 0xffu) + (u32)i < 256u ? f->sec[sr][(a & 0xffu) + (u32)i] : 0xffu); + break; + case 0x75: /* suspend */ + if (busy(f)) { + f->sus_left = f->busy_end - now_ps(f); + sr_clear(f, 0, SR1_BUSY); + sr_set(f, 1, SR2_SUS); + } + break; + case 0x7a: /* resume */ + if (f->sr[1] & SR2_SUS) { + sr_clear(f, 1, SR2_SUS); + start(f, f->sus_left); + f->sus_left = 0; + } + break; + case 0xb9: + f->pd = 1; + break; + case 0xab: + /* the ID after three dummy bytes, repeated: sent as an address or + * counted as dummy clocks, the bytes read are the ID */ + f->pd = 0; + for (i = 0; i < nrx; i++) + rx[i] = NOR_DEVICE_ID; + break; + case 0x90: + case 0x92: + case 0x94: + if (op == 0x94 && !qe) + break; + for (i = 0; i < nrx; i++) + rx[i] = (u8)(((u32)i & 1u) == (a & 1u) ? 0xC4u : NOR_DEVICE_ID); + break; + case 0x9f: + for (i = 0; i < nrx && i < 3; i++) + rx[i] = (u8)(0xC46016u >> (16 - 8 * i)); + break; + case 0x4b: + for (i = 0; i < nrx && i < 8; i++) + rx[i] = unique_id[i]; + break; + case 0x99: + if (f->reset_en) + reset_device(f); + f->reset_en = 0; + break; + default: + break; + } +} + +int esk_esp32_nor_busy(struct esk_esp32_nor *f) +{ + return busy(f); +} + +struct esk_esp32_nor *esk_esp32_nor_create(struct esk_mach *m) +{ + struct esk_esp32_nor *f; + struct esk_store *st = esk_mach_store(m, "flash"); + if (!st || st->size == 0 || (st->size & (st->size - 1u))) + return 0; + f = (struct esk_esp32_nor *)esk_mach_alloc(m, sizeof *f); + if (!f) + return 0; + memset(f, 0, sizeof *f); + f->m = m; + f->st = st; + /* as shipped: no protection, QE 0 (the "G" ordering code), driver + * strength 25% (DRV1-0 = 11b), /HOLD (HOLD/RST 0) */ + f->sr_nv[2] = 0x60; + memcpy(f->sr, f->sr_nv, sizeof f->sr); + memset(f->sec, 0xff, sizeof f->sec); + f->last_cycles = m->cycles; + return f; +} + +/* a power-on: the volatile state from the non-volatile bits (the array, the + * status registers' non-volatile bits and the security registers stay) */ +void esk_esp32_nor_power_on(struct esk_esp32_nor *f) +{ + reset_device(f); + f->pd = f->udpd = f->reset_en = 0; + sr_clear(f, 0, SR1_BUSY | SR1_WEL); + f->last_cycles = f->m->cycles; +} diff --git a/core/mcu/esp32/soc/regi2c.c b/core/mcu/esp32/soc/regi2c.c new file mode 100644 index 000000000..93368e859 --- /dev/null +++ b/core/mcu/esp32/soc/regi2c.c @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* regi2c.c -- the ESP32's internal I2C master to its analog blocks, at + * 0x3FF4E000 (0x6000E000 on the AHB view, where the ROM reaches it): the + * bus over which the BBPLL, the APLL, the bias and the regulators are set + * up. The TRM does not describe it; what is modelled is what the boot + * ROM's rom_chip_i2c_readReg and rom_chip_i2c_writeReg (0x40004110, + * 0x40004168) do with it -- the routines ESP-IDF reaches through + * esp_rom_regi2c_* -- and the registers ESP-IDF's soc/regi2c_*.h name. + * The ESP32-C3's model (mcu/esp32c3/regi2c.c) has the same protocol. + * + * - Host n's control register (+4n, n 0-7): the block's address in bits + * 7:0, the register's in 15:8, the data in 23:16, bit 24 for a write, + * bit 25 busy. A write of it reads or writes the block's register at + * once: busy reads 0, and a read's byte is in bits 23:16. + * - ANA_CONFIG (+0x44, ESP-IDF's ANA_CONFIG_REG: the blocks' I2C enables, + * I2C_BBPLL bit 17, I2C_APLL bit 14) and the words up to +0xFFC: kept as + * written. + * - The blocks keep their registers through every reset but a chip's + * (power-on, CHIP_PU): they are analog, outside the digital system. + * Not yet: the registers' reset values (a block's registers read 0 until + * written), the transfers' time, and what the values do (the PLL's + * frequency comes from DPORT's CPU_PER_CONF and RTC_CNTL's SOC_CLK_SEL, + * the regulators' voltages are not modelled). */ +#include + +#include "mcu/esp32/soc/esp32.h" + +#define REGI2C_BASE 0x3FF4E000u +#define REGI2C_SIZE 0x1000u +#define NBLOCKS 16 /* the blocks' addresses are 0x60 to 0x6F */ + +struct e32_regi2c { + struct esk_mach *s; + u32 word[REGI2C_SIZE / 4u]; + u8 reg[NBLOCKS][256]; +}; + +static u8 *block_reg(struct e32_regi2c *k, u32 block, u32 r) +{ + if (block < 0x60u || block >= 0x60u + NBLOCKS) + return 0; + return &k->reg[block - 0x60u][r & 0xffu]; +} + +static void transfer(struct e32_regi2c *k, u32 w, u32 v) +{ + u32 block = v & 0xffu, r = (v >> 8) & 0xffu, data = (v >> 16) & 0xffu; + u8 *p = block_reg(k, block, r); + if (v & 1u << 24) { + if (p) + *p = (u8)data; + k->word[w / 4u] = v & ~(1u << 25); + } else { + data = p ? *p : 0; + k->word[w / 4u] = (v & ~(0xffu << 16 | 1u << 25)) | data << 16; + } +} + +static u32 ri_read(void *ctx, u32 off, int n) +{ + struct e32_regi2c *k = (struct e32_regi2c *)ctx; + ES_UNUSED(n); + return k->word[(off & ~3u) / 4u] >> (8u * (off & 3u)); +} + +static void ri_write(void *ctx, u32 off, int n, u32 v) +{ + struct e32_regi2c *k = (struct e32_regi2c *)ctx; + u32 w = off & ~3u, sh = 8u * (off & 3u); + u32 mask = (n >= 4 ? 0xffffffffu : ((1u << (8 * n)) - 1u)) << sh; + v = (k->word[w / 4u] & ~mask) | (v << sh & mask); + if (w < 0x20u) + transfer(k, w, v); + else + k->word[w / 4u] = v; +} + +static void ri_reset(void *ctx) +{ + struct e32_regi2c *k = (struct e32_regi2c *)ctx; + if (k->s->reset_cause == ESK_RESET_POWER || k->s->reset_cause == ESK_RESET_PIN) { + memset(k->word, 0, sizeof k->word); + memset(k->reg, 0, sizeof k->reg); + } + memset(k->word, 0, 0x20u); /* the hosts are idle */ +} + +static const struct esk_dev_ops regi2c_ops = { + "esp32-regi2c", ri_read, ri_write, ri_reset, 0, 0, +}; + +int esk_esp32_regi2c_add(struct esk_mach *s) +{ + struct e32_regi2c *k = (struct e32_regi2c *)esk_mach_alloc(s, sizeof *k); + if (!k) + return -1; + k->s = s; + return esk_mach_add_dev(s, REGI2C_BASE, REGI2C_SIZE, ®i2c_ops, k) == ES_OK ? 0 : -1; +} diff --git a/core/mcu/esp32/soc/rtc.c b/core/mcu/esp32/soc/rtc.c new file mode 100644 index 000000000..021337b7f --- /dev/null +++ b/core/mcu/esp32/soc/rtc.c @@ -0,0 +1,398 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* rtc.c -- the ESP32's RTC_CNTL (TRM v5 chapters 31 and 32): the reset + * causes, the RTC timer, the RTC watchdog (RWDT), the software resets and + * stalls, the clock selection the CPU's clock follows, the store + * registers and the RTC interrupts. + * + * - RESET_STATE's RESET_CAUSE_PROCPU and RESET_CAUSE_APPCPU report each + * CPU's last reset by the TRM's code: 1 POWERON for a chip reset + * (power-on, CHIP_PU), 3 SW_RESET (OPTIONS0.SW_SYS_RST), 12 SW_CPU + * (SW_PROCPU_RST, SW_APPCPU_RST), 9, 13 and 16 the RWDT's system, CPU + * and RTC resets, 7 and 8 the timer groups' watchdogs + * (esk_esp32_reset); the APP_CPU reports 14 (EXT_CPU) while it was + * held by the PRO_CPU (DPORT); + * - the RTC domain keeps its registers through CPU and system resets + * and loses them in a chip or RTC reset; a CPU reset restarts that CPU + * alone (esk_lx6_core_restart); + * - the RTC timer counts RTC_SLOW_CLK (CLK_CONF's ANA_CLK_RTC_SEL: the + * 150 kHz RC oscillator, the 32.768 kHz crystal, 8MD256) from the chip's + * reset; a write of TIME_UPDATE's bit 31 latches it in TIME0 and TIME1 + * and sets TIME_VALID; SLP_TIMER0/1 with MAIN_TIMER_ALARM_EN raise + * MAIN_TIMER when it reaches them; + * - the RWDT counts RTC_SLOW_CLK through four stages (WDT_STGn_HOLD); + * each stage's action is off, an interrupt (WDT), a CPU, system or RTC + * reset; WDT_EN runs it, and so does WDT_FLASHBOOT_MOD_EN (set at a + * chip reset) with stage 0 an RTC reset; WDTFEED returns it to stage + * 0; WDTCONFIG0-4 and WDTFEED take writes only while WDT_WKEY holds + * 0x50D83AA1; + * - a CPU stalls while its SW_STALL_*CPU_C1 (SW_CPU_STALL) is 0x21 and + * its C0 (OPTIONS0) 2; + * - INT_RAW, INT_ST = INT_RAW & INT_ENA, INT_CLR, and the matrix source + * RTC_CORE (46). + * Not modelled: sleep and wake-up (light and deep sleep, the power + * domains, the ULP coprocessor), brown-out, the slow clocks' error (the + * RC oscillator is exactly 150 kHz), the reset pulse widths + * (WDT_*_RESET_LENGTH), STAT_VECTOR_SEL (the cores always start at the + * ROM). */ +#include + +#include "mcu/esp32/soc/esp32.h" +#include "mcu/xtensa/lx6.h" + +#define RC_SLOW_HZ 150000.0 +#define RC_FAST_HZ 8000000.0 +#define WDT_KEY 0x50D83AA1u +#define CFG0_FLASHBOOT (1u << 10) +#define CFG0_EN (1u << 31) +#define INT_WDT (1u << 3) +#define INT_MAIN_TIMER (1u << 8) +#define OPT0_SW_APPCPU_RST (1u << 4) +#define OPT0_SW_PROCPU_RST (1u << 5) +#define OPT0_SW_SYS_RST (1u << 31) + +#define OPTIONS0 0x00u +#define SLP_TIMER0 0x04u +#define SLP_TIMER1 0x08u +#define TIME_UPDATE 0x0Cu +#define TIME0 0x10u +#define TIME1 0x14u +#define RESET_STATE 0x34u +#define INT_ENA 0x3Cu +#define INT_RAW 0x40u +#define INT_ST 0x44u +#define INT_CLR 0x48u +#define CLK_CONF 0x70u +#define WDTCONFIG0 0x8Cu +#define WDTFEED 0xA0u +#define WDTWPROTECT 0xA4u +#define SW_CPU_STALL 0xACu + +enum { ACT_OFF, ACT_INT, ACT_CPU, ACT_SYSTEM, ACT_RTC }; + +struct e32_rtc { + struct esk_mach *s; + struct esk_rf_periph *p; + int code[2]; /* the cause each CPU's next reset reports */ + int level; /* the RTC_CORE source */ + int clocked; + u64 slow, base_cycles; + double frac; + int stage; + u64 wdt_from; +}; + +static struct e32_rtc *rtc_of(struct esk_mach *m) +{ + return m->rf ? (struct e32_rtc *)esk_rf_ctx_of(m->rf, &esk_esp32_rtc_ops) : 0; +} + +static u32 reg(struct e32_rtc *k, u32 off) +{ + struct esk_rf_reg *r = esk_rf_reg_at(k->p, off); + return r ? (u32)r->value : 0; +} + +static void hw(struct e32_rtc *k, u32 off, u32 v) +{ + struct esk_rf_reg *r = esk_rf_reg_at(k->p, off); + if (r) + esk_rf_hw(r, v); +} + +static double slow_hz(struct e32_rtc *k) +{ + switch ((reg(k, CLK_CONF) >> 30) & 3u) { + case 1: + return k->s->lse_hz ? (double)k->s->lse_hz : 32768.0; + case 2: + return RC_FAST_HZ / 256.0; + default: + return RC_SLOW_HZ; + } +} + +u32 esk_esp32_rtc_soc_clk_sel(struct esk_mach *m) +{ + struct e32_rtc *k = rtc_of(m); + return k ? (reg(k, CLK_CONF) >> 27) & 3u : 0; +} + +double esk_esp32_rtc_slow_hz(struct esk_mach *m) +{ + struct e32_rtc *k = rtc_of(m); + return k ? slow_hz(k) : RC_SLOW_HZ; +} + +static void sync(struct e32_rtc *k) +{ + u64 now = k->s->cycles; + double t; + if (now <= k->base_cycles) { + k->base_cycles = now; + return; + } + t = (double)(now - k->base_cycles) * slow_hz(k) / (double)esk_mach_core_hz(k->s) + k->frac; + k->slow += (u64)t; + k->frac = t - (double)(u64)t; + k->base_cycles = now; +} + +static u64 cycles_to(struct e32_rtc *k, u64 n) +{ + double c = ((double)n - k->frac) * (double)esk_mach_core_hz(k->s) / slow_hz(k); + return c <= 1.0 ? 1u : (u64)c + 1u; +} + +static int wdt_on(struct e32_rtc *k) +{ + return (reg(k, WDTCONFIG0) & (CFG0_EN | CFG0_FLASHBOOT)) != 0; +} + +static int stage_action(struct e32_rtc *k, int stage) +{ + u32 c = reg(k, WDTCONFIG0); + if (stage == 0 && (c & CFG0_FLASHBOOT)) + return ACT_RTC; /* flash boot protection */ + return (int)((c >> (28 - 3 * stage)) & 7u); +} + +static u64 stage_hold(struct e32_rtc *k, int stage) +{ + u64 h = reg(k, WDTCONFIG0 + 4u * (u32)(stage + 1)); + return h ? h : 1; +} + +static void update(struct e32_rtc *k) +{ + u32 st = reg(k, INT_RAW) & reg(k, INT_ENA); + hw(k, INT_ST, st); + if ((st != 0) != k->level) { + k->level = st != 0; + esk_esp32_source(k->s, esk_rf_irq(k->p, "RTC_CORE"), k->level); + } +} + +static void clocking(struct e32_rtc *k) +{ + int on = wdt_on(k) || (reg(k, SLP_TIMER1) & (1u << 16)); + esk_rf_clock(k->p, &k->clocked, on); +} + +static void expire(struct e32_rtc *k) +{ + int guard = 4; + if (reg(k, SLP_TIMER1) & (1u << 16)) { + u64 alarm = (u64)reg(k, SLP_TIMER0) | (u64)(reg(k, SLP_TIMER1) & 0xffffu) << 32; + if ((k->slow & 0xffffffffffffull) >= alarm) { + hw(k, INT_RAW, reg(k, INT_RAW) | INT_MAIN_TIMER); + hw(k, SLP_TIMER1, reg(k, SLP_TIMER1) & ~(1u << 16)); + } + } + while (wdt_on(k) && k->slow - k->wdt_from >= stage_hold(k, k->stage) && guard--) { + int act = stage_action(k, k->stage); + k->wdt_from += stage_hold(k, k->stage); + k->stage = (k->stage + 1) & 3; + if (act == ACT_INT) { + hw(k, INT_RAW, reg(k, INT_RAW) | INT_WDT); + } else if (act == ACT_CPU) { + esk_esp32_cpu_reset(k->s, 0, 13); + esk_esp32_cpu_reset(k->s, 1, 13); + break; + } else if (act == ACT_SYSTEM) { + esk_esp32_reset(k->s, 9); + break; + } else if (act == ACT_RTC) { + esk_esp32_reset(k->s, 16); + break; + } + } + update(k); + clocking(k); +} + +static void stalls(struct e32_rtc *k) +{ + u32 o = reg(k, OPTIONS0), c1 = reg(k, SW_CPU_STALL); + esk_lx6_core_stall(k->s, 0, 2, ((o >> 2) & 3u) == 2u && ((c1 >> 26) & 0x3fu) == 0x21u); + esk_lx6_core_stall(k->s, 1, 2, (o & 3u) == 2u && ((c1 >> 20) & 0x3fu) == 0x21u); +} + +static u32 rt_read(void *ctx, u32 off, int n) +{ + struct e32_rtc *k = (struct e32_rtc *)ctx; + sync(k); + return esk_rf_read(k->p, off, n); +} + +static void rt_write(void *ctx, u32 off, int n, u32 v) +{ + struct e32_rtc *k = (struct e32_rtc *)ctx; + int keyed = reg(k, WDTWPROTECT) == WDT_KEY; + sync(k); + if (off == WDTFEED) { + if (keyed && (v & (1u << 31))) { + k->stage = 0; + k->wdt_from = k->slow; + } + return; + } + if (off >= WDTCONFIG0 && off <= WDTCONFIG0 + 16u && !keyed) + return; + if (off == INT_CLR) { + hw(k, INT_RAW, reg(k, INT_RAW) & ~v); + update(k); + return; + } + if (off == INT_RAW || off == INT_ST) + return; + if (off == WDTCONFIG0) { + u32 was = reg(k, WDTCONFIG0); + esk_rf_write(k->p, off, n, v); + if (!(was & (CFG0_EN | CFG0_FLASHBOOT)) && wdt_on(k)) { + k->stage = 0; + k->wdt_from = k->slow; + } + } else { + esk_rf_write(k->p, off, n, v); + } + if (off == TIME_UPDATE && (v & (1u << 31))) { + u64 t = k->slow & 0xffffffffffffull; + hw(k, TIME0, (u32)t); + hw(k, TIME1, (u32)(t >> 32)); + hw(k, TIME_UPDATE, (reg(k, TIME_UPDATE) & ~(1u << 31)) | (1u << 30)); + } + if (off == CLK_CONF) + k->s->yield = 1; /* SOC_CLK_SEL: the CPU's clock (apbctrl.c) */ + if (off == OPTIONS0 || off == SW_CPU_STALL) + stalls(k); + if (off == OPTIONS0) { + u32 o = reg(k, OPTIONS0); + hw(k, OPTIONS0, o & ~(OPT0_SW_SYS_RST | OPT0_SW_PROCPU_RST | OPT0_SW_APPCPU_RST)); + if (v & OPT0_SW_SYS_RST) { + esk_esp32_reset(k->s, 3); + return; + } + if (v & OPT0_SW_APPCPU_RST) + esk_esp32_cpu_reset(k->s, 1, 12); + if (v & OPT0_SW_PROCPU_RST) + esk_esp32_cpu_reset(k->s, 0, 12); + } + expire(k); +} + +static void rt_tick(void *ctx, u32 cycles) +{ + struct e32_rtc *k = (struct e32_rtc *)ctx; + ES_UNUSED(cycles); + sync(k); + expire(k); +} + +static int rt_next(void *ctx, u32 *cycles) +{ + struct e32_rtc *k = (struct e32_rtc *)ctx; + u64 best = 0, c; + sync(k); + if (wdt_on(k)) { + u64 hold = stage_hold(k, k->stage), done = k->slow - k->wdt_from; + best = cycles_to(k, hold > done ? hold - done : 1); + } + if (reg(k, SLP_TIMER1) & (1u << 16)) { + u64 alarm = (u64)reg(k, SLP_TIMER0) | (u64)(reg(k, SLP_TIMER1) & 0xffffu) << 32, + now = k->slow & 0xffffffffffffull; + c = cycles_to(k, alarm > now ? alarm - now : 1); + if (!best || c < best) + best = c; + } + if (!best) + return 0; + *cycles = best > 0xffffffffu ? 0xffffffffu : (u32)best; + return 1; +} + +static int rtc_domain_lost(int code) +{ + return code == 1 || code == 16; +} + +static void rt_reset(void *ctx) +{ + struct e32_rtc *k = (struct e32_rtc *)ctx; + int chip = k->s->reset_cause == ESK_RESET_POWER || k->s->reset_cause == ESK_RESET_PIN; + int code = chip ? 1 : k->code[0]; + if (chip || rtc_domain_lost(code)) { + int i; + k->slow = 0; + k->frac = 0; + for (i = 0; i < k->p->nreg; i++) { /* a CHIP_PU reset too: the RTC domain starts again */ + k->p->reg[i]->value = k->p->reg[i]->reset; + k->p->reg[i]->once = 0; + } + } + k->stage = 0; + k->wdt_from = k->slow; + k->base_cycles = 0; + k->clocked = 0; + k->level = 0; + hw(k, RESET_STATE, (reg(k, RESET_STATE) & ~0xfffu) | (u32)code | (u32)(chip ? 1 : k->code[1]) << 6); + k->code[0] = k->code[1] = 1; + k->p->backup = 1; /* kept through the next system reset */ + update(k); + clocking(k); +} + +const struct esk_dev_ops esk_esp32_rtc_ops = { + "esp32-rtc", rt_read, rt_write, rt_reset, rt_tick, rt_next, +}; + +void *esk_esp32_rtc_create(struct esk_mach *s, struct esk_rf_periph *p) +{ + struct e32_rtc *k = (struct e32_rtc *)esk_mach_alloc(s, sizeof *k); + if (!k) + return 0; + k->s = s; + k->p = p; + k->code[0] = k->code[1] = 1; + return k; +} + +/* a reset of the whole digital system (and of the RTC domain for 1, 16) */ +void esk_esp32_reset(struct esk_mach *m, int code) +{ + struct e32_rtc *k = rtc_of(m); + if (k) { + k->code[0] = k->code[1] = code; + if (rtc_domain_lost(code)) + k->p->backup = 0; + } + m->reset_cpu_only = 0; + m->reset_cause = code == 3 ? ESK_RESET_SOFTWARE : ESK_RESET_WATCHDOG; + m->reset_req = 1; + m->yield = 1; +} + +/* one CPU alone: it restarts at its reset vector and reports `code` */ +void esk_esp32_cpu_reset(struct esk_mach *m, int core, int code) +{ + struct e32_rtc *k = rtc_of(m); + if (k) { + u32 rs = reg(k, RESET_STATE); + if (core == 0) + rs = (rs & ~0x3fu) | (u32)code; + else + rs = (rs & ~0xfc0u) | (u32)code << 6; + hw(k, RESET_STATE, rs); + } + esk_lx6_core_restart(m, core); +} + +void esk_esp32_rtc_reset_cause(struct esk_mach *m, int core, int code) +{ + struct e32_rtc *k = rtc_of(m); + if (k) { + u32 rs = reg(k, RESET_STATE); + rs = core == 0 ? (rs & ~0x3fu) | (u32)code : (rs & ~0xfc0u) | (u32)code << 6; + hw(k, RESET_STATE, rs); + } +} diff --git a/core/mcu/esp32/soc/sha.c b/core/mcu/esp32/soc/sha.c new file mode 100644 index 000000000..5e29d7899 --- /dev/null +++ b/core/mcu/esp32/soc/sha.c @@ -0,0 +1,202 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* sha.c -- the ESP32's SHA accelerator (TRM v5 chapter 23): SHA-1, + * SHA-256, SHA-384 and SHA-512 (FIPS 180-4) over message blocks the + * program writes in SHA_TEXT. + * + * - SHA_TEXT (0x00-0x7C) holds a block as 32-bit words in message order, + * each the big-endian value of four message bytes (ESP-IDF's sha_ll + * swaps them in): 16 words for SHA-1 and SHA-256, 32 for SHA-384 and + * SHA-512 (a 64-bit word's high half first); + * - writing 1 to a type's START (0x80, 0x90, 0xA0, 0xB0) hashes the block + * from the type's initial state, CONTINUE (+4) from the state so far; + * LOAD (+8) puts the state in SHA_TEXT, in the same word order, for + * the program to read; BUSY (+0xC) reads 0: each operation is done by + * the time the next access comes (the TRM gives no cycle counts). + * The state is the accelerator's, so a program that hashes with two types + * in turn without LOAD loses nothing it could see. SHA-384's LOAD puts + * its full 512-bit state, of which the digest is the first 384 bits. + * Not yet: DPORT's SHA clock gate and reset (PERI_CLK_EN, PERI_RST_EN). */ +#include + +#include "mcu/esp32/soc/esp32.h" +#include "util/sha256.h" + +#define TEXT_WORDS 32 + +struct e32_sha { + struct esk_mach *s; + struct esk_rf_periph *p; + u32 text[TEXT_WORDS]; + u32 h1[5], h256[8]; + u64 h512[8]; +}; + +static const u32 iv1[5] = { 0x67452301u, 0xefcdab89u, 0x98badcfeu, 0x10325476u, 0xc3d2e1f0u }; +static const u32 iv256[8] = { 0x6a09e667u, 0xbb67ae85u, 0x3c6ef372u, 0xa54ff53au, + 0x510e527fu, 0x9b05688cu, 0x1f83d9abu, 0x5be0cd19u }; +static const u64 iv384[8] = { 0xcbbb9d5dc1059ed8ull, 0x629a292a367cd507ull, 0x9159015a3070dd17ull, + 0x152fecd8f70e5939ull, 0x67332667ffc00b31ull, 0x8eb44a8768581511ull, + 0xdb0c2e0d64f98fa7ull, 0x47b5481dbefa4fa4ull }; +static const u64 iv512[8] = { 0x6a09e667f3bcc908ull, 0xbb67ae8584caa73bull, 0x3c6ef372fe94f82bull, + 0xa54ff53a5f1d36f1ull, 0x510e527fade682d1ull, 0x9b05688c2b3e6c1full, + 0x1f83d9abfb41bd6bull, 0x5be0cd19137e2179ull }; +static const u64 k512[80] = { + 0x428a2f98d728ae22ull, 0x7137449123ef65cdull, 0xb5c0fbcfec4d3b2full, 0xe9b5dba58189dbbcull, + 0x3956c25bf348b538ull, 0x59f111f1b605d019ull, 0x923f82a4af194f9bull, 0xab1c5ed5da6d8118ull, + 0xd807aa98a3030242ull, 0x12835b0145706fbeull, 0x243185be4ee4b28cull, 0x550c7dc3d5ffb4e2ull, + 0x72be5d74f27b896full, 0x80deb1fe3b1696b1ull, 0x9bdc06a725c71235ull, 0xc19bf174cf692694ull, + 0xe49b69c19ef14ad2ull, 0xefbe4786384f25e3ull, 0x0fc19dc68b8cd5b5ull, 0x240ca1cc77ac9c65ull, + 0x2de92c6f592b0275ull, 0x4a7484aa6ea6e483ull, 0x5cb0a9dcbd41fbd4ull, 0x76f988da831153b5ull, + 0x983e5152ee66dfabull, 0xa831c66d2db43210ull, 0xb00327c898fb213full, 0xbf597fc7beef0ee4ull, + 0xc6e00bf33da88fc2ull, 0xd5a79147930aa725ull, 0x06ca6351e003826full, 0x142929670a0e6e70ull, + 0x27b70a8546d22ffcull, 0x2e1b21385c26c926ull, 0x4d2c6dfc5ac42aedull, 0x53380d139d95b3dfull, + 0x650a73548baf63deull, 0x766a0abb3c77b2a8ull, 0x81c2c92e47edaee6ull, 0x92722c851482353bull, + 0xa2bfe8a14cf10364ull, 0xa81a664bbc423001ull, 0xc24b8b70d0f89791ull, 0xc76c51a30654be30ull, + 0xd192e819d6ef5218ull, 0xd69906245565a910ull, 0xf40e35855771202aull, 0x106aa07032bbd1b8ull, + 0x19a4c116b8d2d0c8ull, 0x1e376c085141ab53ull, 0x2748774cdf8eeb99ull, 0x34b0bcb5e19b48a8ull, + 0x391c0cb3c5c95a63ull, 0x4ed8aa4ae3418acbull, 0x5b9cca4f7763e373ull, 0x682e6ff3d6b2b8a3ull, + 0x748f82ee5defb2fcull, 0x78a5636f43172f60ull, 0x84c87814a1f0ab72ull, 0x8cc702081a6439ecull, + 0x90befffa23631e28ull, 0xa4506cebde82bde9ull, 0xbef9a3f7b2c67915ull, 0xc67178f2e372532bull, + 0xca273eceea26619cull, 0xd186b8c721c0c207ull, 0xeada7dd6cde0eb1eull, 0xf57d4f7fee6ed178ull, + 0x06f067aa72176fbaull, 0x0a637dc5a2c898a6ull, 0x113f9804bef90daeull, 0x1b710b35131c471bull, + 0x28db77f523047d84ull, 0x32caab7b40c72493ull, 0x3c9ebe0a15c9bebcull, 0x431d67c49c100d4cull, + 0x4cc5d4becb3e42b6ull, 0x597f299cfc657e2aull, 0x5fcb6fab3ad6faecull, 0x6c44198c4a475817ull }; + +static u32 rol(u32 x, int n) { return (x << n) | (x >> (32 - n)); } +static u64 ror64(u64 x, int n) { return (x >> n) | (x << (64 - n)); } + +static void sha1_block(u32 h[5], const u32 m[16]) +{ + u32 w[80], a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f, k, t; + int i; + for (i = 0; i < 16; i++) + w[i] = m[i]; + for (; i < 80; i++) + w[i] = rol(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); + for (i = 0; i < 80; i++) { + if (i < 20) { f = (b & c) | (~b & d); k = 0x5a827999u; } + else if (i < 40) { f = b ^ c ^ d; k = 0x6ed9eba1u; } + else if (i < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8f1bbcdcu; } + else { f = b ^ c ^ d; k = 0xca62c1d6u; } + t = rol(a, 5) + f + e + k + w[i]; + e = d; d = c; c = rol(b, 30); b = a; a = t; + } + h[0] += a; h[1] += b; h[2] += c; h[3] += d; h[4] += e; +} + +static void sha512_block(u64 h[8], const u32 m[32]) +{ + u64 w[80], v[8], t1, t2; + int i, j; + for (i = 0; i < 16; i++) + w[i] = (u64)m[2 * i] << 32 | m[2 * i + 1]; + for (; i < 80; i++) { + u64 s0 = ror64(w[i - 15], 1) ^ ror64(w[i - 15], 8) ^ (w[i - 15] >> 7); + u64 s1 = ror64(w[i - 2], 19) ^ ror64(w[i - 2], 61) ^ (w[i - 2] >> 6); + w[i] = w[i - 16] + s0 + w[i - 7] + s1; + } + for (j = 0; j < 8; j++) + v[j] = h[j]; + for (i = 0; i < 80; i++) { + t1 = v[7] + (ror64(v[4], 14) ^ ror64(v[4], 18) ^ ror64(v[4], 41)) + ((v[4] & v[5]) ^ (~v[4] & v[6])) + + k512[i] + w[i]; + t2 = (ror64(v[0], 28) ^ ror64(v[0], 34) ^ ror64(v[0], 39)) + + ((v[0] & v[1]) ^ (v[0] & v[2]) ^ (v[1] & v[2])); + v[7] = v[6]; v[6] = v[5]; v[5] = v[4]; v[4] = v[3] + t1; + v[3] = v[2]; v[2] = v[1]; v[1] = v[0]; v[0] = t1 + t2; + } + for (j = 0; j < 8; j++) + h[j] += v[j]; +} + +static void sha256_words(u32 h[8], const u32 m[16]) +{ + u8 blk[64]; + int i; + for (i = 0; i < 16; i++) { + blk[4 * i] = (u8)(m[i] >> 24); + blk[4 * i + 1] = (u8)(m[i] >> 16); + blk[4 * i + 2] = (u8)(m[i] >> 8); + blk[4 * i + 3] = (u8)m[i]; + } + esk_sha256_block(h, blk); +} + +/* type 0 SHA-1, 1 SHA-256, 2 SHA-384, 3 SHA-512; op 0 start, 1 continue, 2 load */ +static void operate(struct e32_sha *k, int type, int op) +{ + int i; + if (op == 2) { + if (type == 0) + for (i = 0; i < 5; i++) + k->text[i] = k->h1[i]; + else if (type == 1) + for (i = 0; i < 8; i++) + k->text[i] = k->h256[i]; + else + for (i = 0; i < 8; i++) { + k->text[2 * i] = (u32)(k->h512[i] >> 32); + k->text[2 * i + 1] = (u32)k->h512[i]; + } + return; + } + if (type == 0) { + if (op == 0) + memcpy(k->h1, iv1, sizeof k->h1); + sha1_block(k->h1, k->text); + } else if (type == 1) { + if (op == 0) + memcpy(k->h256, iv256, sizeof k->h256); + sha256_words(k->h256, k->text); + } else { + if (op == 0) + memcpy(k->h512, type == 2 ? iv384 : iv512, sizeof k->h512); + sha512_block(k->h512, k->text); + } +} + +static u32 sh_read(void *ctx, u32 off, int n) +{ + struct e32_sha *k = (struct e32_sha *)ctx; + if (off < 4u * TEXT_WORDS) + return k->text[off / 4u] >> (8u * (off & 3u)); + if (off >= 0x80u && off < 0xC0u) + return 0; /* START, CONTINUE, LOAD are write-only; BUSY: done */ + return esk_rf_read(k->p, off, n); +} + +static void sh_write(void *ctx, u32 off, int n, u32 v) +{ + struct e32_sha *k = (struct e32_sha *)ctx; + if (off < 4u * TEXT_WORDS) { + if (n == 4) + k->text[off / 4u] = v; + return; + } + if (off >= 0x80u && off < 0xC0u && (off & 0xCu) != 0xCu && (v & 1u)) { + operate(k, (int)((off - 0x80u) / 0x10u), (int)((off & 0xCu) / 4u)); + return; + } + esk_rf_write(k->p, off, n, v); +} + +static void sh_reset(void *ctx) +{ + struct e32_sha *k = (struct e32_sha *)ctx; + memset(k->text, 0, sizeof k->text); +} + +const struct esk_dev_ops esk_esp32_sha_ops = { + "esp32-sha", sh_read, sh_write, sh_reset, 0, 0, +}; + +void *esk_esp32_sha_create(struct esk_mach *s, struct esk_rf_periph *p) +{ + struct e32_sha *k = (struct e32_sha *)esk_mach_alloc(s, sizeof *k); + if (!k) + return 0; + k->s = s; + k->p = p; + return k; +} diff --git a/core/mcu/esp32/soc/spi.c b/core/mcu/esp32/soc/spi.c new file mode 100644 index 000000000..8e57abbf1 --- /dev/null +++ b/core/mcu/esp32/soc/spi.c @@ -0,0 +1,354 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* spi.c -- the ESP32's SPI0 and SPI1 (TRM v5 chapter 7), the controllers + * in front of the module's flash (nor.c): SPI0 the caches', SPI1 the + * program's. + * + * A write of CMD starts the transaction its bit names, on the flash: + * - FLASH_READ: ADDR[31:24] bytes from ADDR[23:0] into W0-W15, with the + * read instruction CTRL selects (FREAD_QIO EBh, FREAD_DIO BBh, + * FREAD_QUAD 6Bh, FREAD_DUAL 3Bh, FASTRD_MODE 0Bh, else 03h); + * - FLASH_PP: ADDR[31:24] bytes from W0 (02h, or 32h with FWRITE_QUAD); + * FLASH_SE (20h), FLASH_BE (D8h), FLASH_CE (C7h) at ADDR; + * - FLASH_WREN (06h), FLASH_WRDI (04h), FLASH_RDSR (05h into + * RD_STATUS), FLASH_WRSR (01h from RD_STATUS: one byte, two with + * WRSR_2B), FLASH_RDID (9Fh into W0), FLASH_DP (B9h), FLASH_RES (ABh), + * FLASH_HPM (A3h), FLASH_PES (75h), FLASH_PER (7Ah); + * - USR: the user transaction USER enables: the command (USER2's + * USR_COMMAND_VALUE, USR_COMMAND_BITLEN + 1 bits), the address + * (ADDR's USR_ADDR_BITLEN + 1 most significant bits), the dummy + * cycles, MOSI_DLEN + 1 bits out of W0 (W8 with USR_MOSI_HIGHPART) and + * MISO_DLEN + 1 bits into W0 (W8 with USR_MISO_HIGHPART). + * The command's bit reads 1 while the transaction is on the bus -- the + * bits it carries at the SPI clock (CLOCK: APB_CLK with CLK_EQU_SYSCLK, + * else APB_CLK / (CLKDIV_PRE + 1) / (CLKCNT_N + 1)), its address and data + * on two or four lines as the mode says -- and EXT2's ST is not idle; + * then SLAVE's TRANS_DONE is set, and with its INT_EN bit the matrix + * source (SPI0 30, SPI1 31 in the register description's table). + * Bytes in the W buffers are little-endian (RD_BYTE_ORDER and + * WR_BYTE_ORDER 0). + * Not modelled: the byte and bit orders' other settings, the controllers' + * DMA and slave modes (SPI0 and SPI1 are masters on the flash), the CRC, + * the flash encryption on SPI1's writes, PSRAM (SPI0's cache path to it), + * the cache's own reads (dport.c reads the flash directly). */ +#include + +#include "mcu/esp32/soc/esp32.h" + +#define CMD 0x00u +#define ADDR 0x04u +#define CTRL 0x08u +#define RD_STATUS 0x10u +#define CLOCK 0x18u +#define USER 0x1Cu +#define USER1 0x20u +#define USER2 0x24u +#define MOSI_DLEN 0x28u +#define MISO_DLEN 0x2Cu +#define SLAVE 0x38u +#define W0 0x80u +#define EXT2 0xF8u + +#define CMD_USR (1u << 18) +#define CMD_HPM (1u << 19) +#define CMD_RES (1u << 20) +#define CMD_DP (1u << 21) +#define CMD_CE (1u << 22) +#define CMD_BE (1u << 23) +#define CMD_SE (1u << 24) +#define CMD_PP (1u << 25) +#define CMD_WRSR (1u << 26) +#define CMD_RDSR (1u << 27) +#define CMD_RDID (1u << 28) +#define CMD_WRDI (1u << 29) +#define CMD_WREN (1u << 30) +#define CMD_READ (1u << 31) +#define CMD_PES (1u << 17) +#define CMD_PER (1u << 16) +#define CMD_ANY 0xFFFF0000u + +struct e32_spi { + struct esk_mach *s; + struct esk_rf_periph *p; + struct esk_esp32_nor *nor; + int unit, source, level; + u32 busy; /* the command bit in progress */ + u64 left; + int clocked; +}; + +static u32 reg(struct e32_spi *k, u32 off) +{ + return esk_rf_read(k->p, off, 4); +} + +static void hw(struct e32_spi *k, u32 off, u32 v) +{ + struct esk_rf_reg *r = esk_rf_reg_at(k->p, off); + if (r) + esk_rf_hw(r, v); +} + +static void buf_get(struct e32_spi *k, u32 at, u8 *d, int n) +{ + int i; + for (i = 0; i < n && i < 64; i++) + d[i] = (u8)(reg(k, W0 + at + (u32)(i & ~3)) >> (8 * (i & 3))); +} + +static void buf_put(struct e32_spi *k, u32 at, const u8 *d, int n) +{ + int i; + for (i = 0; i < n && i < 64; i += 4) { + u32 w = reg(k, W0 + at + (u32)i); + int b; + for (b = 0; b < 4 && i + b < n; b++) + w = (w & ~(0xffu << (8 * b))) | (u32)d[i + b] << (8 * b); + hw(k, W0 + at + (u32)i, w); + } +} + +/* the SPI clock in Hz */ +static double spi_hz(struct e32_spi *k) +{ + u32 c = reg(k, CLOCK); + double apb = (double)esk_esp32_apb_hz(k->s); + if (c & (1u << 31)) + return apb; + return apb / (double)(((c >> 18) & 0x1fffu) + 1u) / (double)(((c >> 12) & 0x3fu) + 1u); +} + +/* the flash read instruction CTRL selects, and its data lines */ +static u8 read_op(struct e32_spi *k, int *lines) +{ + u32 c = reg(k, CTRL); + *lines = 1; + if (c & (1u << 24)) { *lines = 4; return 0xEB; } + if (c & (1u << 23)) { *lines = 2; return 0xBB; } + if (c & (1u << 20)) { *lines = 4; return 0x6B; } + if (c & (1u << 14)) { *lines = 2; return 0x3B; } + if (c & (1u << 13)) + return 0x0B; + return 0x03; +} + +static void addr3(u8 *tx, u32 a) +{ + tx[1] = (u8)(a >> 16); + tx[2] = (u8)(a >> 8); + tx[3] = (u8)a; +} + +static void update(struct e32_spi *k) +{ + u32 sl = reg(k, SLAVE); + int lv = ((sl & 0x1fu) & ((sl >> 5) & 0x1fu)) != 0; + if (lv != k->level && k->source >= 0) { + k->level = lv; + esk_esp32_source(k->s, k->source, lv); + } +} + +/* the transaction: the flash's part now, its bus time in SPI clocks */ +static u64 run(struct e32_spi *k, u32 cmd) +{ + u8 tx[80], rx[64]; + u32 a = reg(k, ADDR), len = a >> 24, user = reg(k, USER); + int lines = 1, n; + u64 clocks = 8; + memset(tx, 0, sizeof tx); + memset(rx, 0xff, sizeof rx); + if (len > 64) + len = 64; + if (cmd == CMD_READ) { + tx[0] = read_op(k, &lines); + addr3(tx, a); + esk_esp32_nor_xfer(k->nor, tx, 4, rx, (int)len); + buf_put(k, 0, rx, (int)len); + return clocks + (24u + 8u * len) / (u32)lines + (tx[0] == 0x03 ? 0u : 8u); + } + if (cmd == CMD_PP) { + tx[0] = (user & (1u << 13)) ? 0x32 : 0x02; + addr3(tx, a); + buf_get(k, 0, tx + 4, (int)len); + esk_esp32_nor_xfer(k->nor, tx, 4 + (int)len, rx, 0); + return clocks + 24u + 8u * len; + } + if (cmd == CMD_SE || cmd == CMD_BE) { + tx[0] = cmd == CMD_SE ? 0x20 : 0xD8; + addr3(tx, a); + esk_esp32_nor_xfer(k->nor, tx, 4, rx, 0); + return clocks + 24u; + } + if (cmd == CMD_RDSR) { + tx[0] = 0x05; + esk_esp32_nor_xfer(k->nor, tx, 1, rx, 1); + hw(k, RD_STATUS, (reg(k, RD_STATUS) & ~0xffu) | rx[0]); + return clocks + 8u; + } + if (cmd == CMD_WRSR) { + u32 st = reg(k, RD_STATUS); + int two = (reg(k, CTRL) & (1u << 22)) != 0; + tx[0] = 0x01; + tx[1] = (u8)st; + tx[2] = (u8)(st >> 8); + esk_esp32_nor_xfer(k->nor, tx, two ? 3 : 2, rx, 0); + return clocks + (two ? 16u : 8u); + } + if (cmd == CMD_RDID) { + tx[0] = 0x9F; + esk_esp32_nor_xfer(k->nor, tx, 1, rx, 3); + hw(k, W0, (u32)rx[0] | (u32)rx[1] << 8 | (u32)rx[2] << 16); + return clocks + 24u; + } + if (cmd != CMD_USR) { + tx[0] = cmd == CMD_WREN ? 0x06 : cmd == CMD_WRDI ? 0x04 : cmd == CMD_CE ? 0xC7 : cmd == CMD_DP ? 0xB9 + : cmd == CMD_RES ? 0xAB : cmd == CMD_HPM ? 0xA3 : cmd == CMD_PES ? 0x75 : 0x7A; + esk_esp32_nor_xfer(k->nor, tx, 1, rx, 0); + return clocks; + } + /* a user transaction */ + n = 0; + clocks = 0; + if (user & (1u << 31)) { + u32 bits = ((reg(k, USER2) >> 28) & 15u) + 1u, v = reg(k, USER2) & 0xffffu; + int nb = (int)((bits + 7u) / 8u), i; + for (i = 0; i < nb; i++) + tx[n++] = (u8)(v >> (8 * i)); + clocks += bits; + } + if (user & (1u << 30)) { + u32 bits = ((reg(k, USER1) >> 26) & 0x3fu) + 1u; + int nb = (int)((bits + 7u) / 8u), i; + for (i = 0; i < nb && i < 4; i++) + tx[n++] = (u8)(a >> (24 - 8 * i)); + clocks += bits; + } + if (user & (1u << 29)) + clocks += (reg(k, USER1) & 0xffu) + 1u; + if (user & (1u << 27)) { + u32 bits = (reg(k, MOSI_DLEN) & 0xffffffu) + 1u; + int nb = (int)((bits + 7u) / 8u); + if (nb > 64) + nb = 64; + buf_get(k, (user & (1u << 25)) ? 32u : 0u, tx + n, nb); + n += nb; + clocks += bits; + } + { + int nrx = 0; + if (user & (1u << 28)) { + u32 bits = (reg(k, MISO_DLEN) & 0xffffffu) + 1u; + nrx = (int)((bits + 7u) / 8u); + if (nrx > 64) + nrx = 64; + clocks += bits; + } + if (n) + esk_esp32_nor_xfer(k->nor, tx, n, rx, nrx); + if (nrx) + buf_put(k, (user & (1u << 24)) ? 32u : 0u, rx, nrx); + } + return clocks; +} + +static void done(struct e32_spi *k) +{ + k->busy = 0; + hw(k, CMD, reg(k, CMD) & ~CMD_ANY); + hw(k, EXT2, 0); + hw(k, SLAVE, reg(k, SLAVE) | (1u << 4)); /* TRANS_DONE */ + esk_rf_clock(k->p, &k->clocked, 0); + update(k); +} + +static u32 sp_read(void *ctx, u32 off, int n) +{ + struct e32_spi *k = (struct e32_spi *)ctx; + return esk_rf_read(k->p, off, n); +} + +static void sp_write(void *ctx, u32 off, int n, u32 v) +{ + struct e32_spi *k = (struct e32_spi *)ctx; + esk_rf_write(k->p, off, n, v); + if (off == CMD && !k->busy && (v & CMD_ANY)) { + u32 cmd = 0x80000000u, clocks; + while (cmd && !(v & cmd)) + cmd >>= 1; + clocks = (u32)run(k, cmd); + k->busy = cmd; + hw(k, CMD, cmd); + hw(k, EXT2, 1); + k->left = (u64)((double)clocks * (double)esk_mach_core_hz(k->s) / spi_hz(k)) + 1u; + esk_rf_clock(k->p, &k->clocked, 1); + } else if (off == CMD && k->busy) { + hw(k, CMD, (reg(k, CMD) & ~CMD_ANY) | k->busy); + } else if (off == SLAVE) { + update(k); + } +} + +static void sp_tick(void *ctx, u32 cycles) +{ + struct e32_spi *k = (struct e32_spi *)ctx; + if (!k->busy) + return; + if (k->left > cycles) { + k->left -= cycles; + return; + } + done(k); +} + +static int sp_next(void *ctx, u32 *cycles) +{ + struct e32_spi *k = (struct e32_spi *)ctx; + if (!k->busy) + return 0; + *cycles = k->left > 0xffffffffu ? 0xffffffffu : (u32)k->left; + return 1; +} + +static void sp_reset(void *ctx) +{ + struct e32_spi *k = (struct e32_spi *)ctx; + k->busy = 0; + k->clocked = 0; + k->level = 0; + if (k->unit == 1 && k->nor && k->s->reset_cause == ESK_RESET_POWER) + esk_esp32_nor_power_on(k->nor); +} + +const struct esk_dev_ops esk_esp32_spi_ops = { + "esp32-spi", sp_read, sp_write, sp_reset, sp_tick, sp_next, +}; + +void *esk_esp32_spi_create(struct esk_mach *s, struct esk_rf_periph *p) +{ + struct e32_spi *k; + if (strcmp(p->name, "SPI0") && strcmp(p->name, "SPI1")) + return 0; + k = (struct e32_spi *)esk_mach_alloc(s, sizeof *k); + if (!k) + return 0; + k->s = s; + k->p = p; + k->unit = p->name[3] - '0'; + k->source = esk_rf_irq(p, p->name); + k->nor = esk_esp32_nor_of(s); + if (!k->nor) + return 0; + return k; +} + +/* the one flash chip both controllers reach, made by whichever comes first */ +struct esk_esp32_nor *esk_esp32_nor_of(struct esk_mach *m) +{ + struct esk_rf_periph *p; + int i; + for (i = 0; m->rf && (p = esk_rf_periph_at(m->rf, i)) != 0; i++) + if (p->ops == &esk_esp32_spi_ops && p->ctx && ((struct e32_spi *)p->ctx)->nor) + return ((struct e32_spi *)p->ctx)->nor; + return esk_esp32_nor_create(m); +} diff --git a/core/mcu/esp32/soc/timg.c b/core/mcu/esp32/soc/timg.c new file mode 100644 index 000000000..7aa7a9b46 --- /dev/null +++ b/core/mcu/esp32/soc/timg.c @@ -0,0 +1,434 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* timg.c -- the ESP32's timer groups TIMG0 and TIMG1 (TRM v5 chapters 18 + * and 19): in each, the 64-bit timers T0 and T1, the LAC timer (LACT), the + * main system watchdog (MWDT) and the RTC_SLOW_CLK calibration. The + * ESP32-C3's model (mcu/esp32c3/timg.c) on the ESP32's registers. + * + * - T0, T1 and LACT count APB_CLK over DIVIDER (0 is 65536, 1 is 2), up + * or down (INCREASE) over 64 bits while EN is set. A write to UPDATE + * latches the count in LO/HI; one to LOAD loads LOADLO/HI. With + * ALARM_EN a timer alarms when it reaches ALARMLO/HI, or at once when + * the alarm is up to 2^63 behind it in its direction; the alarm clears + * ALARM_EN, sets its INT_RAW bit and reloads with AUTORELOAD. + * - The MWDT counts APB_CLK over WDT_CLK_PRESCALE through four stages + * (WDTCONFIG2-5's holds), each's action off, an interrupt (WDT's + * INT_RAW bit), a CPU reset (TGWDT_CPU_RESET, 11) or a system reset + * (TG0WDT_SYS_RESET 7, TG1WDT_SYS_RESET 8); WDT_EN runs it. With + * WDT_FLASHBOOT_MOD_EN, set out of reset in TIMG0, it runs with stage + * 0 a system reset. WDTFEED returns it to stage 0. While WDTWPROTECT + * is not 0x50D83AA1 the WDT registers ignore writes. + * - RTCCALICFG's START counts MAX cycles of the clock CLK_SEL names + * (RTC_SLOW_CLK as RTC_CNTL selects it, 8MD256's 31.25 kHz, the + * board's 32.768 kHz crystal) against the 40 MHz crystal once; when + * done RDY is set and RTCCALICFG1's VALUE holds the crystal's count; + * a MAX of 0 sets RDY at the clock's next cycle (taken one period + * away). START_CYCLING counts again and again. A clock that does not run (no + * 32 kHz crystal) never makes RDY. + * - INT_ST is INT_RAW and INT_ENA; each bit drives its level source + * (TIMG0's T0 14, T1 15, WDT 16, LACT 17; TIMG1's 18-21), and an alarm + * whose EDGE_INT_EN is set pulses its edge source (TIMG0 51-54, TIMG1 + * 55-58). + * Inside, the RC oscillators run at their nominal frequencies. + * Not yet: LACT's RTC step mode (LACTRTC, RTC_ONLY, CPST_EN: it counts + * APB_CLK), the reset signals' lengths. */ +#include + +#include "mcu/esp32/soc/esp32.h" + +#define HALF63 (1ull << 63) +#define WKEY 0x50D83AA1u + +#define T_ALARM_EN (1u << 10) +#define T_LEVEL_INT_EN (1u << 11) +#define T_EDGE_INT_EN (1u << 12) +#define T_AUTORELOAD (1u << 29) +#define T_INCREASE (1u << 30) +#define T_EN (1u << 31) + +#define W0_FLASHBOOT (1u << 14) +#define W0_EN (1u << 31) + +#define CALI_CYCLING (1u << 12) +#define CALI_RDY (1u << 15) +#define CALI_START (1u << 31) + +#define WDTCONFIG0 0x48u +#define WDTFEED 0x60u +#define WPROTECT 0x64u +#define RTCCALICFG 0x68u +#define RTCCALICFG1 0x6Cu +#define INT_ENA 0x98u +#define INT_RAW 0x9Cu +#define INT_ST 0xA0u +#define INT_CLR 0xA4u + +/* each timer's registers: config, lo, hi, update, alarm lo/hi, load lo/hi, load */ +static const u32 tbase[3] = { 0x00, 0x24, 0x70 }; + +struct e32_timg { + struct esk_mach *s; + struct esk_rf_periph *p; + int group, level[4], clocked; + u64 value[3], frac[3]; + int stage; + u64 wcount, w_frac; + int cali_on; + u64 cali_x, cali_need, x_frac; + u64 cali_wait; /* MAX 0: the crystal's cycles to the next slow cycle */ +}; + +static u32 rd(struct e32_timg *k, u32 off) { return esk_rf_read(k->p, off, 4); } + +static void hw(struct e32_timg *k, u32 off, u32 v) +{ + struct esk_rf_reg *r = esk_rf_reg_at(k->p, off); + if (r) + esk_rf_hw(r, v); +} + +/* a timer's register: 0 config, 1 lo, 2 hi, 3 update, 4/5 alarm, 6/7 load, 8 load trigger */ +static u32 toff(int t, int r) +{ + return tbase[t] + (t == 2 && r > 0 ? 8u : 0u) + 4u * (u32)r - (t == 2 && r > 0 ? 4u : 0u); +} + +static void update(struct e32_timg *k) +{ + u32 st = rd(k, INT_RAW) & rd(k, INT_ENA) & 15u; + int i; + hw(k, INT_ST, st); + for (i = 0; i < 4; i++) { + int l = (int)(st >> i & 1u); + if (l != k->level[i]) { + k->level[i] = l; + esk_esp32_source(k->s, (k->group ? 18 : 14) + i, l); + } + } +} + +static void edge(struct e32_timg *k, int i) +{ + int src = (k->group ? 55 : 51) + i; + esk_esp32_source(k->s, src, 1); + esk_esp32_source(k->s, src, 0); +} + +static u64 ticks(struct e32_timg *k, u32 cycles, u32 src_hz, u32 div, u64 *frac) +{ + u64 hz = (u64)esk_mach_core_hz(k->s) * div, n; + if (!hz) + return 0; + *frac += (u64)cycles * src_hz; + n = *frac / hz; + *frac %= hz; + return n; +} + +static u32 t_div(struct e32_timg *k, int t) +{ + u32 d = (rd(k, toff(t, 0)) >> 13) & 0xffffu; + return d == 0 ? 65536u : d == 1 ? 2u : d; +} + +static u64 pair(struct e32_timg *k, u32 lo) +{ + return (u64)rd(k, lo + 4u) << 32 | rd(k, lo); +} + +static void t_alarm(struct e32_timg *k, int t) +{ + u32 c = rd(k, toff(t, 0)); + hw(k, toff(t, 0), c & ~T_ALARM_EN); + hw(k, INT_RAW, rd(k, INT_RAW) | (t == 2 ? 8u : 1u << t)); + if (c & T_AUTORELOAD) + k->value[t] = pair(k, toff(t, 6)); + if (c & T_EDGE_INT_EN) + edge(k, t == 2 ? 3 : t); +} + +static u64 t_ahead(struct e32_timg *k, int t) +{ + u64 a = pair(k, toff(t, 4)), d; + d = rd(k, toff(t, 0)) & T_INCREASE ? a - k->value[t] : k->value[t] - a; + return d > HALF63 ? 0 : d; +} + +static void t_run(struct e32_timg *k, int t, u32 cycles) +{ + u64 n; + if (!(rd(k, toff(t, 0)) & T_EN)) + return; + n = ticks(k, cycles, esk_esp32_apb_hz(k->s), t_div(k, t), &k->frac[t]); + while (n) { + u64 step = n; + u32 c = rd(k, toff(t, 0)); + if (c & T_ALARM_EN) { + u64 d = t_ahead(k, t); + if (d == 0) { + t_alarm(k, t); + continue; + } + if (d <= n) + step = d; + } + k->value[t] = c & T_INCREASE ? k->value[t] + step : k->value[t] - step; + n -= step; + if ((rd(k, toff(t, 0)) & T_ALARM_EN) && t_ahead(k, t) == 0) + t_alarm(k, t); + } +} + +/* ---- the MWDT ------------------------------------------------------------ */ + +static u32 w0(struct e32_timg *k) { return rd(k, WDTCONFIG0); } +static int flashboot(struct e32_timg *k) { return k->group == 0 && (w0(k) & W0_FLASHBOOT); } +static int wdt_on(struct e32_timg *k) { return (w0(k) & W0_EN) || flashboot(k); } +static u32 prescale(struct e32_timg *k) +{ + u32 p = (rd(k, WDTCONFIG0 + 4u) >> 16) & 0xffffu; + return p ? p : 65536u; +} +static u64 hold(struct e32_timg *k, int s) { return rd(k, WDTCONFIG0 + 8u + 4u * (u32)s); } + +static u32 stage_action(struct e32_timg *k, int s) +{ + if (s == 0 && flashboot(k)) + return 3; + return (w0(k) >> (29 - 2 * s)) & 3u; +} + +static void wdt_expire(struct e32_timg *k) +{ + u32 act = stage_action(k, k->stage); + k->wcount = 0; + k->stage = (k->stage + 1) & 3; + if (act == 1) { + hw(k, INT_RAW, rd(k, INT_RAW) | 4u); + if (w0(k) & (1u << 22)) + edge(k, 2); + } else if (act == 2) { + esk_esp32_cpu_reset(k->s, 0, 11); + esk_esp32_cpu_reset(k->s, 1, 11); + } else if (act == 3) { + esk_esp32_reset(k->s, k->group ? 8 : 7); + } +} + +static void wdt_run(struct e32_timg *k, u32 cycles) +{ + u64 n; + int guard = 8; + if (!wdt_on(k)) + return; + n = ticks(k, cycles, esk_esp32_apb_hz(k->s), prescale(k), &k->w_frac); + while (n && guard--) { + u64 h = hold(k, k->stage), left = h > k->wcount ? h - k->wcount : 0; + if (n < left) { + k->wcount += n; + return; + } + n -= left; + wdt_expire(k); + if (k->s->reset_req) + return; + } +} + +/* ---- the calibration ----------------------------------------------------- */ + +static double cali_hz(struct e32_timg *k) +{ + u32 sel = (rd(k, RTCCALICFG) >> 13) & 3u; + return sel == 0 ? esk_esp32_rtc_slow_hz(k->s) : sel == 1 ? 8000000.0 / 256.0 : (double)k->s->lse_hz; +} + +static void cali_start(struct e32_timg *k, int periodic) +{ + double hz = cali_hz(k); + u32 max = (rd(k, RTCCALICFG) >> 16) & 0x7fffu; + k->cali_on = periodic ? 2 : 1; + k->cali_x = 0; + k->cali_need = hz > 0 ? (u64)((double)max * ESP32_XTAL_HZ / hz + 0.5) : 0; + k->cali_wait = 0; + if (hz > 0 && max == 0) { + /* none counted: RDY at the clock's next cycle (ESP-IDF's + * rtc_clk_wait_for_slow_cycle), taken as one period away */ + k->cali_wait = (u64)((double)ESP32_XTAL_HZ / hz + 0.5); + } + if (!periodic) + hw(k, RTCCALICFG, rd(k, RTCCALICFG) & ~CALI_RDY); +} + +static u64 cali_end(struct e32_timg *k) +{ + return k->cali_need ? k->cali_need : k->cali_wait; +} + +static void cali_run(struct e32_timg *k, u32 cycles) +{ + if (!k->cali_on || !cali_end(k)) + return; + k->cali_x += ticks(k, cycles, ESP32_XTAL_HZ, 1, &k->x_frac); + if (k->cali_x < cali_end(k)) + return; + hw(k, RTCCALICFG1, (u32)(k->cali_need & 0x1ffffffu) << 7); + hw(k, RTCCALICFG, rd(k, RTCCALICFG) | CALI_RDY); + if (k->cali_on == 2) + k->cali_x %= cali_end(k); + else + k->cali_on = 0; +} + +/* ---- the device ---------------------------------------------------------- */ + +static int any_on(struct e32_timg *k) +{ + int t; + for (t = 0; t < 3; t++) + if (rd(k, toff(t, 0)) & T_EN) + return 1; + return wdt_on(k) || (k->cali_on && cali_end(k)); +} + +static void clocking(struct e32_timg *k) +{ + esk_rf_clock(k->p, &k->clocked, any_on(k)); +} + +static void tg_tick(void *ctx, u32 cycles) +{ + struct e32_timg *k = (struct e32_timg *)ctx; + int t; + for (t = 0; t < 3; t++) + t_run(k, t, cycles); + wdt_run(k, cycles); + cali_run(k, cycles); + update(k); + clocking(k); +} + +static u64 to_cycles(struct e32_timg *k, u64 n, double src_hz, u64 div, u64 frac) +{ + double c = ((double)n * (double)esk_mach_core_hz(k->s) * (double)div - (double)frac) / src_hz; + if (src_hz <= 0 || c > 4.0e9) + return 0xffffffffu; + return c < 1.0 ? 1u : (u64)c + 1u; +} + +static int tg_next(void *ctx, u32 *cycles) +{ + struct e32_timg *k = (struct e32_timg *)ctx; + u64 best = 0, c; + int any = 0, t; + for (t = 0; t < 3; t++) { + u32 conf = rd(k, toff(t, 0)); + if ((conf & T_EN) && (conf & T_ALARM_EN)) { + u64 d = t_ahead(k, t); + c = d ? to_cycles(k, d, (double)esk_esp32_apb_hz(k->s), t_div(k, t), k->frac[t]) : 1; + if (!any || c < best) + best = c; + any = 1; + } + } + if (wdt_on(k)) { + u64 h = hold(k, k->stage), left = h > k->wcount ? h - k->wcount : 1; + c = to_cycles(k, left, (double)esk_esp32_apb_hz(k->s), prescale(k), k->w_frac); + if (!any || c < best) + best = c; + any = 1; + } + if (k->cali_on && cali_end(k)) { + u64 left = cali_end(k) > k->cali_x ? cali_end(k) - k->cali_x : 1; + c = to_cycles(k, left, (double)ESP32_XTAL_HZ, 1, k->x_frac); + if (!any || c < best) + best = c; + any = 1; + } + if (!any) + return 0; + *cycles = best == 0 ? 1u : best > 0xffffffffu ? 0xffffffffu : (u32)best; + return 1; +} + +static u32 tg_read(void *ctx, u32 off, int n) +{ + struct e32_timg *k = (struct e32_timg *)ctx; + return esk_rf_read(k->p, off, n); +} + +static void tg_write(void *ctx, u32 off, int n, u32 v) +{ + struct e32_timg *k = (struct e32_timg *)ctx; + u32 cali0 = rd(k, RTCCALICFG), conf[3]; + int t; + if (((off >= WDTCONFIG0 && off <= WDTFEED)) && rd(k, WPROTECT) != WKEY) + return; /* write-protected */ + for (t = 0; t < 3; t++) + conf[t] = rd(k, toff(t, 0)); + esk_rf_write(k->p, off, n, v); + for (t = 0; t < 3; t++) { + if (off == toff(t, 3)) { + hw(k, toff(t, 1), (u32)k->value[t]); + hw(k, toff(t, 2), (u32)(k->value[t] >> 32)); + } else if (off == toff(t, 8)) { + k->value[t] = pair(k, toff(t, 6)); + } else if (off == toff(t, 0)) { + if ((rd(k, off) & T_ALARM_EN) && !(conf[t] & T_ALARM_EN) && t_ahead(k, t) == 0) + t_alarm(k, t); + } + } + if (off == WDTFEED) { + k->stage = 0; + k->wcount = 0; + } else if (off == WDTCONFIG0 && !(v & W0_EN) && !(v & W0_FLASHBOOT)) { + k->stage = 0; + k->wcount = 0; + } else if (off == RTCCALICFG) { + u32 now = rd(k, RTCCALICFG); + if ((now & CALI_START) && !(cali0 & CALI_START)) + cali_start(k, 0); + else if ((now & CALI_CYCLING) && !(cali0 & CALI_CYCLING) && !(now & CALI_START)) + cali_start(k, 1); + else if (!(now & (CALI_START | CALI_CYCLING))) + k->cali_on = 0; + } else if (off == INT_CLR) { + hw(k, INT_RAW, rd(k, INT_RAW) & ~(v & 15u)); + } + update(k); + clocking(k); +} + +static void tg_reset(void *ctx) +{ + struct e32_timg *k = (struct e32_timg *)ctx; + int t; + for (t = 0; t < 3; t++) + k->value[t] = k->frac[t] = 0; + k->stage = 0; + k->wcount = k->w_frac = 0; + k->cali_on = 0; + k->cali_x = k->x_frac = 0; + k->clocked = 0; + memset(k->level, 0, sizeof k->level); + update(k); + clocking(k); +} + +const struct esk_dev_ops esk_esp32_timg_ops = { + "esp32-timg", tg_read, tg_write, tg_reset, tg_tick, tg_next, +}; + +void *esk_esp32_timg_create(struct esk_mach *s, struct esk_rf_periph *p) +{ + struct e32_timg *k; + if (strcmp(p->name, "TIMG0") && strcmp(p->name, "TIMG1")) + return 0; + k = (struct e32_timg *)esk_mach_alloc(s, sizeof *k); + if (!k) + return 0; + k->s = s; + k->p = p; + k->group = p->name[4] == '1'; + return k; +} diff --git a/core/mcu/esp32/soc/uart.c b/core/mcu/esp32/soc/uart.c new file mode 100644 index 000000000..349132885 --- /dev/null +++ b/core/mcu/esp32/soc/uart.c @@ -0,0 +1,452 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* uart.c -- the ESP32's UART0, UART1 and UART2 (ESP32 TRM v5, chapter + * 13), over the register file, at their baud rate (the ESP32-C3's model, + * mcu/esp32c3/uart.c, on the ESP32's registers). + * + * - the clock is APB_CLK, or REF_TICK's 1 MHz while CONF0's + * TICK_REF_ALWAYS_ON is clear; a bit is CLKDIV + FRAG / 16 of its + * cycles; + * - a write of FIFO (here or at its AHB address) queues a byte in the TX + * FIFO (128 bytes times MEM_CONF's TX_SIZE; a write to a full FIFO is + * lost); while nCTS lets it (TX_FLOW_EN) the transmitter sends a frame + * at a time: a start bit, 5 to 8 data bits (BIT_NUM), the parity + * (PARITY_EN, PARITY odd) and 1, 1.5 or 2 stop bits (STOP_BIT_NUM); + * frames in the FIFO go out back to back, and once it has emptied the + * line stays idle TX_IDLE_NUM bit times before the next transfer; + * TXD_BRK sends TX_BRK_NUM NULL characters once the FIFO is empty; + * - bytes arrive from the session's serial input or the RX pin into the + * RX FIFO (128 times RX_SIZE); a frame with no room is lost + * (RXFIFO_OVF); a read of FIFO takes the oldest byte; LOOPBACK sends + * the transmitter's frames to its own receiver; + * - INT_RAW's bits: RXFIFO_FULL while more than RXFIFO_FULL_THRHD bytes + * wait, TXFIFO_EMPTY while fewer than TXFIFO_EMPTY_THRHD do, + * PARITY_ERR and FRM_ERR for a received frame, RXFIFO_OVF, BRK_DET, + * RXFIFO_TOUT after RX_TOUT_THRHD times eight bit times of idle line + * with bytes waiting (RX_TOUT_EN; ESP-IDF's uart_ll converts its + * symbols so), TX_BRK_DONE, TX_BRK_IDLE_DONE, TX_DONE; INT_CLR clears + * them (a level condition sets its bit again while it lasts); INT_ST + * is INT_RAW and INT_ENA, and raises the matrix source (UART0 34, + * UART1 35, UART2 36) while nonzero; + * - STATUS reads the FIFOs' counts (with MEM_CNT_STATUS's high bits) + * and the lines; RXFIFO_RST and TXFIFO_RST empty the FIFOs while set. + * Not modelled: autobaud, IrDA, RS485, the AT_CMD character, software + * flow control (XON/XOFF), the RX glitch filter, light-sleep wake-up, + * UHCI, the FIFO RAM's sharing between the UARTs beyond its sizes. */ +#include + +#include "mcu/esp32/soc/esp32.h" + +#define BLOCK 128 /* bytes of FIFO RAM per block (TRM 26.4.2) */ +#define RAW_RXFIFO_FULL (1u << 0) +#define RAW_TXFIFO_EMPTY (1u << 1) +#define RAW_PARITY_ERR (1u << 2) +#define RAW_FRM_ERR (1u << 3) +#define RAW_RXFIFO_OVF (1u << 4) +#define RAW_BRK_DET (1u << 7) +#define RAW_RXFIFO_TOUT (1u << 8) +#define RAW_TX_BRK_DONE (1u << 12) +#define RAW_TX_BRK_IDLE_DONE (1u << 13) +#define RAW_TX_DONE (1u << 14) +#define CONF0_PARITY (1u << 0) +#define CONF0_PARITY_EN (1u << 1) +#define CONF0_TXD_BRK (1u << 8) +#define CONF0_LOOPBACK (1u << 14) +#define CONF0_TX_FLOW_EN (1u << 15) +#define CONF0_RXFIFO_RST (1u << 17) +#define CONF0_TXFIFO_RST (1u << 18) +#define CONF1_RX_TOUT_EN (1u << 31) +#define CONF0_TICK_REF (1u << 27) +#define REF_TICK_HZ 1000000.0 + +struct e32_uart { + struct esk_mach *s; + struct esk_rf_periph *p; + struct esk_rf_reg *fifo, *raw, *st, *ena, *clr, *clkdiv, *status, *conf0, *conf1, *idle, *mem, + *memcnt; + int unit, source, level; + u8 tx[4 * BLOCK], rx[2 * BLOCK]; + int ntx, nrx; + int busy; /* a frame, a break or the idle gap is on the TX line */ + int clocked; /* the device's clock runs (esk_rf_clock) */ + int phase; /* 0 a frame, 1 the idle after it, 2 a break */ + int brk_left; /* NULL characters of a break still to send */ + u64 left, since; + u64 rx_idle; /* bit times of idle RX line, for RXFIFO_TOUT */ + int rx_any; /* a byte arrived since the FIFO was last empty */ + int cts; /* nCTS high: the peer is not ready */ +}; + +static u32 reg(const struct esk_rf_reg *r) { return (u32)r->value; } + +static int tx_cap(const struct e32_uart *u) +{ + int n = (int)((reg(u->mem) >> 7) & 15u); + return (n ? (n > 8 ? 8 : n) : 1) * BLOCK; +} + +static int rx_cap(const struct e32_uart *u) +{ + int n = (int)((reg(u->mem) >> 3) & 15u); + return (n ? (n > 8 ? 8 : n) : 1) * BLOCK; +} + +/* the UART's clock, in Hz */ +static double sclk_hz(const struct e32_uart *u) +{ + return (reg(u->conf0) & CONF0_TICK_REF) ? (double)esk_esp32_apb_hz(u->s) : REF_TICK_HZ; +} + +/* a bit's time in core cycles */ +static u32 bit_cycles(const struct e32_uart *u) +{ + u32 d = reg(u->clkdiv), div = d & 0xfffffu, frag = (d >> 20) & 15u; + double per = (double)div + (double)frag / 16.0, baud, c; + if (per < 1.0) + per = 1.0; + baud = sclk_hz(u) / per; + c = (double)esk_mach_core_hz(u->s) / baud; + return c < 1.0 ? 1u : (u32)(c + 0.5); +} + +/* RXFIFO_TOUT's threshold in bit times: RX_TOUT_THRHD (with MEM_CONF's + * three high bits) times eight */ +static u64 timeout_bits(const struct e32_uart *u) +{ + u64 t = ((reg(u->conf1) >> 24) & 0x7fu) | (u64)((reg(u->mem) >> 18) & 7u) << 7; + return t * 8u; +} + +static int data_bits(const struct e32_uart *u) { return 5 + (int)((reg(u->conf0) >> 2) & 3u); } + +/* STOP_BIT_NUM: 1 one bit, 2 one and a half, 3 two */ +static int stop_halves(const struct e32_uart *u) +{ + u32 n = (reg(u->conf0) >> 4) & 3u; + return n == 2 ? 3 : n == 3 ? 4 : 2; +} + +static int nbits(const struct e32_uart *u) { return data_bits(u) + ((reg(u->conf0) & CONF0_PARITY_EN) ? 1 : 0); } + +static u32 with_parity(const struct e32_uart *u, u32 c) +{ + int n = data_bits(u), ones = 0, i; + c &= (1u << n) - 1u; + if (!(reg(u->conf0) & CONF0_PARITY_EN)) + return c; + for (i = 0; i < n; i++) + ones += (int)((c >> i) & 1u); + /* PARITY 0 even, 1 odd */ + return c | (u32)((ones & 1) ^ ((reg(u->conf0) & CONF0_PARITY) ? 1 : 0)) << n; +} + +static int parity_ok(const struct e32_uart *u, u32 bits) +{ + int n = data_bits(u); + if (!(reg(u->conf0) & CONF0_PARITY_EN)) + return 1; + return with_parity(u, bits) == (bits & ((2u << n) - 1u)); +} + +/* the level conditions, INT_ST and the matrix source */ +static void update(struct e32_uart *u) +{ + u32 raw = reg(u->raw), c1 = reg(u->conf1); + if ((u32)u->nrx > (c1 & 0x7fu)) + raw |= RAW_RXFIFO_FULL; + if ((u32)u->ntx < ((c1 >> 8) & 0x7fu)) + raw |= RAW_TXFIFO_EMPTY; + esk_rf_hw(u->raw, raw); + esk_rf_hw(u->st, raw & reg(u->ena)); + esk_rf_hw(u->status, (reg(u->status) & ~(0xffu | 0xffu << 16)) | ((u32)u->nrx & 0xffu) | + ((u32)u->ntx & 0xffu) << 16); + if (u->memcnt) + esk_rf_hw(u->memcnt, (u32)(u->nrx >> 8) | (u32)(u->ntx >> 8) << 3); + if ((reg(u->st) != 0) != u->level) { + u->level = reg(u->st) != 0; + esk_esp32_source(u->s, u->source, u->level); + } +} + +static void set_raw(struct e32_uart *u, u32 bits) +{ + esk_rf_hw(u->raw, reg(u->raw) | bits); +} + +static void receive(struct e32_uart *u, u32 bits, int framing_error, int brk); + +/* the clock runs while the line is busy or a receive timeout counts */ +static void clocking(struct e32_uart *u) +{ + int on = u->busy || (u->rx_any && (reg(u->conf1) & CONF1_RX_TOUT_EN)); + if (on != u->clocked) { + if (on) + u->since = u->s->cycles; + esk_rf_clock(u->p, &u->clocked, on); + } +} + +static void run(struct e32_uart *u, u64 cycles) +{ + u->left = cycles; + u->since = u->s->cycles; + u->busy = 1; + clocking(u); +} + +/* the next thing on the TX line: a frame, or a break once the FIFO is empty */ +static void tx_next(struct e32_uart *u) +{ + struct esk_mach *s = u->s; + u64 b; + int n; + if (u->busy) + return; + b = bit_cycles(u); + n = nbits(u); + if (u->ntx && !((reg(u->conf0) & CONF0_TX_FLOW_EN) && u->cts)) { + u32 c = u->tx[0], bits = with_parity(u, c); + memmove(u->tx, u->tx + 1, (size_t)(u->ntx - 1)); + u->ntx--; + u->phase = 0; + run(u, b * (u64)(1 + n) + (b * (u64)stop_halves(u) + 1) / 2); + if (s->host.uart_frame) + s->host.uart_frame(s->host.ctx, u->p->name, (int)(c & ((1u << data_bits(u)) - 1u)), bits, n, + stop_halves(u), (u32)b); + else if (s->host.uart_tx) + s->host.uart_tx(s->host.ctx, u->p->name, (int)c, (u32)b); + if (reg(u->conf0) & CONF0_LOOPBACK) + receive(u, bits, 0, 0); + return; + } + if (!u->ntx && u->brk_left > 0) { + u->phase = 2; + u->brk_left--; + run(u, b * (u64)(1 + n + 1)); + if (s->host.uart_frame) + s->host.uart_frame(s->host.ctx, u->p->name, -1, 0, n + 1, 2, (u32)b); + } +} + +/* ---- the receiver ------------------------------------------------------------------ */ + +static void receive(struct e32_uart *u, u32 bits, int framing_error, int brk) +{ + int n = data_bits(u); + if (reg(u->conf0) & CONF0_RXFIFO_RST) + return; + u->rx_idle = 0; + if (brk) { + set_raw(u, RAW_BRK_DET); + } else { + if (!parity_ok(u, bits)) + set_raw(u, RAW_PARITY_ERR); + if (framing_error) + set_raw(u, RAW_FRM_ERR); + if (u->nrx < rx_cap(u)) { + u->rx[u->nrx++] = (u8)(bits & ((1u << n) - 1u)); + u->rx_any = 1; + } else { + set_raw(u, RAW_RXFIFO_OVF); + } + } + clocking(u); /* the timeout counts in ticks */ + update(u); +} + +static int ur_room(void *ctx) +{ + const struct e32_uart *u = (const struct e32_uart *)ctx; + return !(reg(u->conf0) & CONF0_RXFIFO_RST); +} + +static void ur_put(void *ctx, int c) +{ + struct e32_uart *u = (struct e32_uart *)ctx; + receive(u, with_parity(u, (u32)c), 0, 0); +} + +static u32 ur_format(void *ctx) +{ + const struct e32_uart *u = (const struct e32_uart *)ctx; + return (u32)nbits(u) | (u32)stop_halves(u) << 8; +} + +static void ur_put_frame(void *ctx, u32 bits, int framing_error) +{ + struct e32_uart *u = (struct e32_uart *)ctx; + /* all zeros with a framing error: a break */ + receive(u, bits, framing_error, framing_error && !bits); +} + +static void ur_started(void *ctx) +{ + struct e32_uart *u = (struct e32_uart *)ctx; + u->rx_idle = 0; +} + +static u32 ur_bit_cycles(void *ctx) { return bit_cycles((const struct e32_uart *)ctx); } + +/* ---- the registers -------------------------------------------------------------------- */ + +static u32 ur_read(void *ctx, u32 off, int n) +{ + struct e32_uart *u = (struct e32_uart *)ctx; + if (off == esk_rf_off(u->fifo)) { + u32 v; + if (!u->nrx) + return 0; + v = u->rx[0]; + if (!u->s->bus.debug) { + memmove(u->rx, u->rx + 1, (size_t)(u->nrx - 1)); + u->nrx--; + if (!u->nrx) + u->rx_any = 0; + update(u); + } + return v; + } + return esk_rf_read(u->p, off, n); +} + +static void ur_write(void *ctx, u32 off, int n, u32 v) +{ + struct e32_uart *u = (struct e32_uart *)ctx; + if (off == esk_rf_off(u->fifo)) { + if (!(reg(u->conf0) & CONF0_TXFIFO_RST) && u->ntx < tx_cap(u)) + u->tx[u->ntx++] = (u8)v; + } else if (off == esk_rf_off(u->clr)) { + esk_rf_hw(u->raw, reg(u->raw) & ~v); + } else if (off == esk_rf_off(u->raw) || off == esk_rf_off(u->st) || off == esk_rf_off(u->status)) { + /* read only */ + } else { + u32 was0 = reg(u->conf0); + esk_rf_write(u->p, off, n, v); + if (off == esk_rf_off(u->conf0)) { + if (reg(u->conf0) & CONF0_TXFIFO_RST) + u->ntx = 0; + if (reg(u->conf0) & CONF0_RXFIFO_RST) { + u->nrx = 0; + u->rx_any = 0; + } + if ((reg(u->conf0) & CONF0_TXD_BRK) && !(was0 & CONF0_TXD_BRK)) + u->brk_left = (int)((reg(u->idle) >> 20) & 0xffu); + } + } + tx_next(u); + update(u); +} + +static void ur_tick(void *ctx, u32 cycles) +{ + struct e32_uart *u = (struct e32_uart *)ctx; + u64 b = bit_cycles(u); + cycles = esk_mach_after(u->s, cycles, u->since); + if (u->rx_any && (reg(u->conf1) & CONF1_RX_TOUT_EN)) { + u64 thr = timeout_bits(u); + u->rx_idle += cycles; + if (u->rx_idle >= thr * b && u->nrx) { + set_raw(u, RAW_RXFIFO_TOUT); + u->rx_any = 0; + update(u); + } + } + if (!u->busy) { + clocking(u); + return; + } + if (u->left > cycles) { + u->left -= cycles; + return; + } + u->left = 0; + if (u->phase == 0) { + /* the frame is out; the FIFO empty, the transfer is over: at least + * TX_IDLE_NUM bit times of idle line before the next */ + u64 idle = (reg(u->idle) >> 10) & 0x3ffu; + if (!u->ntx) + set_raw(u, RAW_TX_DONE); + if (idle && !u->ntx) { + u->phase = 1; + u->left = idle * b; + u->since = u->s->cycles; + update(u); + return; + } + } else if (u->phase == 2) { + if (u->brk_left <= 0) + set_raw(u, RAW_TX_BRK_DONE | RAW_TX_BRK_IDLE_DONE); + } + u->busy = 0; + tx_next(u); + clocking(u); + update(u); +} + +static int ur_next(void *ctx, u32 *cycles) +{ + struct e32_uart *u = (struct e32_uart *)ctx; + u64 c = u->busy ? u->left : 0; + if (u->rx_any && (reg(u->conf1) & CONF1_RX_TOUT_EN)) { + u64 thr = timeout_bits(u) * bit_cycles(u); + u64 t = thr > u->rx_idle ? thr - u->rx_idle : 1; + if (!c || t < c) + c = t; + } + if (!c) + return 0; + *cycles = c > 0xffffffffu ? 0xffffffffu : (u32)c; + return 1; +} + +static void ur_reset(void *ctx) +{ + struct e32_uart *u = (struct e32_uart *)ctx; + u->ntx = u->nrx = 0; + u->busy = 0; + u->clocked = 0; + u->left = 0; + u->brk_left = 0; + u->rx_any = 0; + u->rx_idle = 0; + u->level = 0; + u->cts = 0; + update(u); +} + +const struct esk_dev_ops esk_esp32_uart_ops = { + "esp32-uart", ur_read, ur_write, ur_reset, ur_tick, ur_next, +}; + +void *esk_esp32_uart_create(struct esk_mach *s, struct esk_rf_periph *p) +{ + struct e32_uart *c; + if (strcmp(p->name, "UART0") && strcmp(p->name, "UART1") && strcmp(p->name, "UART2")) + return 0; + c = (struct e32_uart *)esk_mach_alloc(s, sizeof *c); + if (!c) + return 0; + memset(c, 0, sizeof *c); + c->s = s; + c->p = p; + c->unit = p->name[4] - '0'; + c->source = esk_rf_irq(p, p->name); + c->fifo = esk_rf_reg(p, "FIFO"); + c->raw = esk_rf_reg(p, "INT_RAW"); + c->st = esk_rf_reg(p, "INT_ST"); + c->ena = esk_rf_reg(p, "INT_ENA"); + c->clr = esk_rf_reg(p, "INT_CLR"); + c->clkdiv = esk_rf_reg(p, "CLKDIV"); + c->status = esk_rf_reg(p, "STATUS"); + c->conf0 = esk_rf_reg(p, "CONF0"); + c->conf1 = esk_rf_reg(p, "CONF1"); + c->idle = esk_rf_reg(p, "IDLE_CONF"); + c->mem = esk_rf_reg(p, "MEM_CONF"); + c->memcnt = esk_rf_reg(p, "MEM_CNT_STATUS"); + if (!c->fifo || !c->raw || !c->st || !c->ena || !c->clr || !c->clkdiv || !c->status || !c->conf0 || + !c->conf1 || !c->idle || !c->mem) + return 0; + esk_mach_rx_port(s, p->name, ur_room, ur_put, ur_bit_cycles, c); + esk_mach_rx_frames(s, ur_format, ur_put_frame, ur_started); + return c; +} diff --git a/core/mcu/models.c b/core/mcu/models.c index a2dbc1c5f..954533580 100644 --- a/core/mcu/models.c +++ b/core/mcu/models.c @@ -5,6 +5,7 @@ #include #include "mcu/avr/avr.h" +#include "mcu/esp32/soc/esp32.h" #include "mcu/esp32c3/esp32c3.h" #include "mcu/nrf52/nrf52.h" #include "mcu/pic24/pic24.h" @@ -61,6 +62,16 @@ static const struct esk_rf_model_type model_types[] = { { "fe310-aon", &esk_fe310_aon_ops, esk_fe310_aon_create }, { "fe310-pwm", &esk_fe310_pwm_ops, esk_fe310_pwm_create }, { "fe310-spi", &esk_fe310_spi_ops, esk_fe310_spi_create }, + { "esp32-dport", &esk_esp32_dport_ops, esk_esp32_dport_create }, + { "esp32-efuse", &esk_esp32_efuse_ops, esk_esp32_efuse_create }, + { "esp32-rtc", &esk_esp32_rtc_ops, esk_esp32_rtc_create }, + { "esp32-uart", &esk_esp32_uart_ops, esk_esp32_uart_create }, + { "esp32-apbctrl", &esk_esp32_apbctrl_ops, esk_esp32_apbctrl_create }, + { "esp32-gpio", &esk_esp32_gpio_ops, esk_esp32_gpio_create }, + { "esp32-spi", &esk_esp32_spi_ops, esk_esp32_spi_create }, + { "esp32-timg", &esk_esp32_timg_ops, esk_esp32_timg_create }, + { "esp32-sha", &esk_esp32_sha_ops, esk_esp32_sha_create }, + { "esp32-iomux", &esk_esp32_iomux_ops, esk_esp32_iomux_create }, { "esp32c3-intmtx", &esk_esp32c3_intmtx_ops, esk_esp32c3_intmtx_create }, { "esp32c3-uart", &esk_esp32c3_uart_ops, esk_esp32c3_uart_create }, { "esp32c3-rtc", &esk_esp32c3_rtc_ops, esk_esp32c3_rtc_create }, diff --git a/core/mcu/rf.c b/core/mcu/rf.c index fb8fa4750..09688157d 100644 --- a/core/mcu/rf.c +++ b/core/mcu/rf.c @@ -853,6 +853,15 @@ struct esk_rf_reg *esk_rf_reg(struct esk_rf_periph *p, const char *name) return 0; } +struct esk_rf_reg *esk_rf_reg_at(struct esk_rf_periph *p, u32 off) +{ + int i; + for (i = 0; p && i < p->nreg; i++) + if (p->reg[i]->addr - p->base == off) + return p->reg[i]; + return 0; +} + u64 esk_rf_mask(const struct esk_rf_reg *r, const char *field) { int i; diff --git a/core/mcu/rf.h b/core/mcu/rf.h index 71286b003..9afc95be6 100644 --- a/core/mcu/rf.h +++ b/core/mcu/rf.h @@ -221,6 +221,8 @@ struct esk_rf_periph *esk_rf_periph_at(struct esk_rf *rf, int i); * answer is kept, as a model's context does not move once created */ void *esk_rf_ctx_of(struct esk_rf *rf, const void *ops); struct esk_rf_reg *esk_rf_reg(struct esk_rf_periph *p, const char *name); +/* the register at offset `off` from the peripheral's base, or 0 */ +struct esk_rf_reg *esk_rf_reg_at(struct esk_rf_periph *p, u32 off); /* the bits of the register's field of that name, or 0 */ u64 esk_rf_mask(const struct esk_rf_reg *r, const char *field); /* the program's read and write, as the description says the fields behave */ diff --git a/core/mcu/xtensa/lx6.c b/core/mcu/xtensa/lx6.c new file mode 100644 index 000000000..f6df523be --- /dev/null +++ b/core/mcu/xtensa/lx6.c @@ -0,0 +1,2244 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* lx6.c -- the ESP32's Xtensa LX6 cores (Cadence's Xtensa Instruction Set + * Architecture Reference Manual; the configuration of Espressif's + * core-isa.h for the ESP32, esp32_v3_49_prod, LX6.0.3), two of them as + * the chip's PRO_CPU and APP_CPU behind the machine's one core interface. + * + * The configuration, as core-isa.h states it: + * - the core instructions with the density (16-bit) instructions, + * zero-overhead loops, NSA/NSAU, MIN/MAX, SEXT, CLAMPS, MUL16, MULL, + * MULUH/MULSH, QUOS/QUOU/REMS/REMU, ADDX/SUBX, ABS, L32AI/S32RI, + * S32C1I with SCOMPARE1 and ATOMCTL, booleans (16), THREADPTR, four + * MISC registers, PRID (0xCDCD on PRO_CPU, 0xABAB on APP_CPU), the + * external registers' RER/WER, the MAC16 package and the single + * precision floating-point coprocessor (CP0, lx6fp.c); + * - the windowed registers: 64 physical, WindowBase and WindowStart, + * CALL4/8/12, ENTRY, RETW, MOVSP, ROTW, the window overflow check of + * every instruction while PS.WOE is set and PS.EXCM clear (the frame + * of the first live window above the registers it names spilled by + * the WindowOverflow4/8/12 vector), RETW's underflow, RFWO/RFWU, and + * L32E/S32E for the handlers; + * - the exception architecture XEA2: EPC1, EXCCAUSE, EXCVADDR, the + * kernel, user and double exception vectors, DEPC; relocatable + * vectors (VECBASE, 0x40000000 at reset); + * - 32 interrupts at core-isa.h's levels (1-5, the debug level 6, the + * NMI at 7) and types (level, edge, software 7 and 29, timers 6, 15 + * and 16 from CCOMPARE0-2 against CCOUNT, profiling 11), INTERRUPT, + * INTSET, INTCLEAR, INTENABLE, EXCMLEVEL 3, RSIL, WAITI, RFI; + * - the debug option's BREAK, BREAK.N, two IBREAKs, two DBREAKs and + * ICOUNT, raising the debug exception (level 6) with DEBUGCAUSE; + * - region protection: eight 512 MB regions in each of the instruction + * and data TLBs, attribute 2 at reset, 15 a region no access reaches + * (InstFetchProhibited, LoadProhibited, StoreProhibited); + * - unaligned loads and stores done by the hardware (no alignment + * exception); the ESP32's instruction bus (0x40000000-0x40BFFFFF: + * ROM, IRAM, the flash's instruction window) takes 32-bit aligned + * loads and stores only, others raising LoadStoreError, as the + * chip's TRM says (chapter 1.3.2.2); + * - no MMU, no Xtensa caches (the cache instructions do nothing), one + * ring, MEMCTL's loop buffer as storage (it changes no result). + * An access nothing answers raises the PIF address error (14 for a + * fetch, 15 for a load or store), and is reported (esk_mach_bus_error). + * + * Where it differs inside: each instruction takes an estimated time (one + * cycle, a taken branch, jump, call or return three, a load one, MULL + * and the high products two, a division twelve, an exception's entry + * three); the pipeline, its interlocks and the loop buffer's timing are + * not modelled. CCOUNT counts those cycles. + * + * Two cores share the machine as mcu/cortexm/smp.c's do: each keeps its + * own time, the one furthest behind runs next, the devices see the + * earliest. APP_CPU starts held in reset, its clock gated, until the + * part's DPORT model releases it (esk_lx6_core_reset, _stall). + * + * Written for EmbSIM from the ISA reference and core-isa.h; it shares no + * code with the experimental engine in windows.c and execute.c, whose + * notes on the ESP32's interrupt levels and types it agrees with. */ +#include +#include +#include +#include + +#include "mcu/xtensa/lx6.h" + +#define NCORES 2 + +/* PS */ +#define PS_INTLEVEL 0xfu +#define PS_EXCM 0x10u +#define PS_UM 0x20u +#define PS_OWB_SHIFT 8 +#define PS_OWB (0xfu << PS_OWB_SHIFT) +#define PS_CALLINC_SHIFT 16 +#define PS_CALLINC (3u << PS_CALLINC_SHIFT) +#define PS_WOE 0x40000u +#define PS_MASK 0x00070f3fu + +/* EXCCAUSE */ +#define EXC_ILLEGAL 0u +#define EXC_SYSCALL 1u +#define EXC_LOADSTORE 3u +#define EXC_LEVEL1 4u +#define EXC_ALLOCA 5u +#define EXC_DIVZERO 6u +#define EXC_IFETCH_ADDR 14u +#define EXC_LS_ADDR 15u +#define EXC_IFETCH_PROHIBITED 20u +#define EXC_LOAD_PROHIBITED 28u +#define EXC_STORE_PROHIBITED 29u +#define EXC_CP0_DISABLED 32u + +/* DEBUGCAUSE */ +#define DBG_ICOUNT 0x1u +#define DBG_IBREAK 0x2u +#define DBG_DBREAK 0x4u +#define DBG_BREAK 0x8u +#define DBG_BREAKN 0x10u + +/* the interrupts' types and levels (core-isa.h) */ +#define INT_SOFTWARE 0x20000080u +#define INT_EDGE 0x50400400u +#define INT_NMI 0x00004000u +#define INT_LATCHED (INT_SOFTWARE | INT_EDGE | INT_NMI) +static const u32 level_mask[8] = { 0, 0x000637ffu, 0x00380000u, 0x28c08800u, 0x53000000u, + 0x84010000u, 0, 0x00004000u }; +static const int timer_int[3] = { 6, 15, 16 }; +#define EXCM_LEVEL 3u +#define DEBUG_LEVEL 6u + +/* the vectors' offsets from VECBASE */ +#define VEC_OF4 0x000u +#define VEC_UF4 0x040u +#define VEC_OF8 0x080u +#define VEC_UF8 0x0c0u +#define VEC_OF12 0x100u +#define VEC_UF12 0x140u +static const u32 level_vec[8] = { 0, 0, 0x180u, 0x1c0u, 0x200u, 0x240u, 0x280u, 0x2c0u }; +#define VEC_KERNEL 0x300u +#define VEC_USER 0x340u +#define VEC_DOUBLE 0x3c0u + +#define CONFIGID0 0xC2BCFFFEu +#define CONFIGID1 0x1CC5FE96u + +struct smp; + +struct lx6 { + struct esk_cpu cpu; /* first */ + struct esk_mach *m; + struct smp *x; + int id; + u32 ar[64]; + u32 pc, npc; + u32 wb, ws; /* WindowBase, WindowStart */ + u32 ps, sar, lbeg, lend, lcount, br, scompare1; + u32 acclo, acchi, mr[4]; + u32 epc[8], eps[8], excsave[8], depc, exccause, excvaddr, debugcause; + u32 intenable, lines, latched; + u32 vecbase, ccompare[3]; + u64 ccount_at; /* the core's time CCOUNT counts from */ + u32 ccount_base; + u32 cpenable, threadptr, fcr, fsr, f[16], misc[4], prid, atomctl, memctl; + u32 expstate, f64r_lo, f64r_hi, f64s; /* the ESP32's double-precision accelerator's state */ + u32 ibreakenable, ibreaka[2], dbreaka[2], dbreakc[2], icount, icountlevel, ddr; + u32 itlb[8], dtlb[8]; + u32 reset_vector; + /* the core's own view of read-only memory, ahead of the bus (the + * ESP32's APP_CPU's ROM): base, size and bytes */ + struct { + u32 base, size; + const u8 *mem; + } view[2]; + int nview; + int exc; /* the instruction took an exception: nothing more of it */ + int jumped; /* it branched, jumped, called or returned: no loop-back */ + int waiti; /* waiting in WAITI */ + u32 cost; + char xml[1]; /* no target description */ +}; + +enum { CORE_RUN, CORE_SLEEP, CORE_HELD, CORE_STALLED }; + +struct smp { + struct esk_cpu cpu; /* first */ + struct esk_mach *m; + int n; + struct lx6 *c[NCORES]; + int state[NCORES]; + int held[NCORES], stalled[NCORES]; + u64 lt[NCORES]; + int cur, stepping; + int running; +}; + +/* ---- registers -------------------------------------------------------- */ + +#define AR(c, i) ((c)->ar[((c)->wb * 4u + (u32)(i)) & 63u]) + +static u32 sext(u32 v, int bits) +{ + u32 b = 1u << (bits - 1); + v &= (b << 1) - 1u; + return (v ^ b) - b; +} + +/* the core's time: the machine's while its instruction runs */ +static u64 now(const struct lx6 *c) +{ + return c->m->cycles; +} + +static u32 ccount(const struct lx6 *c) +{ + return c->ccount_base + (u32)(now(c) - c->ccount_at); +} + +static u32 interrupt_reg(const struct lx6 *c) +{ + return (c->lines & ~INT_LATCHED) | c->latched; +} + +/* WindowStart turned so that bit 0 is WindowBase's */ +static u32 ws_rot(const struct lx6 *c) +{ + u32 w = c->ws | c->ws << 16; + return (w >> c->wb) & 0xffffu; +} + +/* ---- exceptions ------------------------------------------------------- */ + +static void to(struct lx6 *c, u32 pc) +{ + c->npc = pc; + c->exc = 1; +} + +static void exception(struct lx6 *c, u32 cause) +{ + c->exccause = cause; + if (c->ps & PS_EXCM) { + c->depc = c->pc; + to(c, c->vecbase + VEC_DOUBLE); + } else { + c->epc[1] = c->pc; + to(c, c->vecbase + ((c->ps & PS_UM) ? VEC_USER : VEC_KERNEL)); + c->ps |= PS_EXCM; + } + c->cost += 2; + esk_mach_exc(c->m, c->id, (int)cause, c->pc); +} + +static void mem_exception(struct lx6 *c, u32 cause, u32 a) +{ + c->excvaddr = a; + exception(c, cause); +} + +void esk_lx6_illegal(struct lx6 *c) +{ + exception(c, EXC_ILLEGAL); +} + +/* a high-priority interrupt or the debug exception at `level` */ +static void level_entry(struct lx6 *c, u32 level, u32 epc) +{ + c->epc[level] = epc; + c->eps[level] = c->ps; + c->ps = (c->ps & ~PS_INTLEVEL) | level | PS_EXCM; + to(c, c->vecbase + level_vec[level]); + c->cost += 2; +} + +static void debug_exception(struct lx6 *c, u32 cause) +{ + if ((c->ps & PS_INTLEVEL) >= DEBUG_LEVEL) + return; + c->debugcause = cause; + level_entry(c, DEBUG_LEVEL, c->pc); + esk_mach_exc(c->m, c->id, 64 + (int)DEBUG_LEVEL, c->pc); +} + +/* the window overflow check: registers up to `hi` must be the frame's */ +static int win(struct lx6 *c, u32 hi) +{ + u32 n, w, owb, k; + if (hi < 4 || (c->ps & (PS_WOE | PS_EXCM)) != PS_WOE) + return 0; + w = ws_rot(c) >> 1; + if (!(w & ((1u << (hi >> 2)) - 1u))) + return 0; + /* the first live frame above is spilled: rotate to it */ + for (n = 1; !(w & 1u); n++) + w >>= 1; + w >>= 1; + owb = c->wb; + c->wb = (c->wb + n) & 15u; + c->ps = (c->ps & ~PS_OWB) | owb << PS_OWB_SHIFT | PS_EXCM; + c->epc[1] = c->pc; + for (k = 0; k < 3 && !(w & 1u); k++) + w >>= 1; + to(c, c->vecbase + (k == 0 ? VEC_OF4 : k == 1 ? VEC_OF8 : VEC_OF12)); + c->cost += 2; + return 1; +} + +/* ---- memory ----------------------------------------------------------- */ + +static int prohibited(u32 attr) +{ + return attr == 15u; +} + +/* the ESP32's instruction bus: 32-bit aligned data accesses only */ +static int ibus_narrow(u32 a, int n) +{ + return a >= 0x40000000u && a < 0x40c00000u && (n != 4 || (a & 3u)); +} + +static int dbreak_hit(struct lx6 *c, u32 a, int n, int store) +{ + int i; + for (i = 0; i < 2; i++) { + u32 ctl = c->dbreakc[i], mask; + if (!(ctl & (store ? 0x80000000u : 0x40000000u))) + continue; + mask = ~(ctl & 0x3fu); + if (((a ^ c->dbreaka[i]) & mask) == 0 || + (((a + (u32)n - 1u) ^ c->dbreaka[i]) & mask) == 0) { + if ((c->ps & PS_INTLEVEL) < DEBUG_LEVEL) { + c->debugcause = DBG_DBREAK | (u32)i << 8; + level_entry(c, DEBUG_LEVEL, c->pc); + return 1; + } + } + } + return 0; +} + +int esk_lx6_load(struct lx6 *c, u32 a, int n, u32 *v) +{ + struct esk_bus *b = &c->m->bus; + struct esk_region *r; + int i; + *v = 0; + if (prohibited(c->dtlb[a >> 29])) { + mem_exception(c, EXC_LOAD_PROHIBITED, a); + return -1; + } + if (ibus_narrow(a, n)) { + mem_exception(c, EXC_LOADSTORE, a); + return -1; + } + if (b->nwatch && esk_bus_watch_check(b, a, n, 0)) { + c->m->yield = 1; + c->exc = 2; /* not run: the debugger stops before it */ + return -1; + } + if ((c->dbreakc[0] | c->dbreakc[1]) && dbreak_hit(c, a, n, 0)) + return -1; + for (i = 0; i < c->nview; i++) + if (a - c->view[i].base < c->view[i].size && c->view[i].size - (a - c->view[i].base) >= (u32)n) { + const u8 *p = c->view[i].mem + (a - c->view[i].base); + u32 x = 0; + int j; + for (j = n - 1; j >= 0; j--) + x = x << 8 | p[j]; + *v = x; + return 0; + } + r = esk_bus_region(b, a, (u32)n); + if (r) { + const u8 *p = r->mem + (a - r->base); + u32 x = 0; + for (i = n - 1; i >= 0; i--) + x = x << 8 | p[i]; + *v = x; + if (b->trace) + esk_bus_traced(b, a, n, 0, x, 0); + return 0; + } + for (i = 0; i < n;) { + u32 x; + int k = n - i >= 4 && !((a + (u32)i) & 3u) ? 4 : n - i >= 2 && !((a + (u32)i) & 1u) ? 2 : 1; + if (esk_bus_read(b, a + (u32)i, k, &x)) { + esk_bus_traced(b, a + (u32)i, k, 0, 0, 1); + esk_mach_bus_error(c->m, a, n, 0); + mem_exception(c, EXC_LS_ADDR, a); + return -1; + } + esk_bus_traced(b, a + (u32)i, k, 0, x, 0); + *v |= x << (8 * i); + i += k; + } + return 0; +} + +int esk_lx6_store(struct lx6 *c, u32 a, int n, u32 v) +{ + struct esk_bus *b = &c->m->bus; + struct esk_region *r; + int i; +#ifdef LX6_DEBUG_TRACE + if (getenv("LX6_WATCH") && a == (u32)strtoul(getenv("LX6_WATCH"), 0, 16)) + fprintf(stderr, "WATCH core %d pc %08x writes %08x a0=%08x\n", c->id, (unsigned)c->pc, (unsigned)v, (unsigned)AR(c, 0)); +#endif + if (prohibited(c->dtlb[a >> 29])) { + mem_exception(c, EXC_STORE_PROHIBITED, a); + return -1; + } + if (ibus_narrow(a, n)) { + mem_exception(c, EXC_LOADSTORE, a); + return -1; + } + if (b->nwatch && esk_bus_watch_check(b, a, n, 1)) { + c->m->yield = 1; + c->exc = 2; + return -1; + } + if ((c->dbreakc[0] | c->dbreakc[1]) && dbreak_hit(c, a, n, 1)) + return -1; + r = esk_bus_region(b, a, (u32)n); + if (r) { + if (!r->rom) { + u8 *p = r->mem + (a - r->base); + for (i = 0; i < n; i++) + p[i] = (u8)(v >> (8 * i)); + } + if (b->trace) + esk_bus_traced(b, a, n, 1, v, 0); + return 0; + } + for (i = 0; i < n;) { + int k = n - i >= 4 && !((a + (u32)i) & 3u) ? 4 : n - i >= 2 && !((a + (u32)i) & 1u) ? 2 : 1; + u32 x = v >> (8 * i); + if (k < 4) + x &= (1u << (8 * k)) - 1u; + if (esk_bus_write(b, a + (u32)i, k, x)) { + esk_bus_traced(b, a + (u32)i, k, 1, x, 1); + esk_mach_bus_error(c->m, a, n, 1); + mem_exception(c, EXC_LS_ADDR, a); + return -1; + } + esk_bus_traced(b, a + (u32)i, k, 1, x, 0); + i += k; + } + return 0; +} + +/* the instruction's bytes at pc: 0, or -1 (nothing answers) */ +static int fetch(struct lx6 *c, u32 pc, u32 *w) +{ + struct esk_bus *b = &c->m->bus; + struct esk_region *r; + u32 lo, hi = 0; + int i; + for (i = 0; i < c->nview; i++) + if (pc - c->view[i].base < c->view[i].size && c->view[i].size - (pc - c->view[i].base) >= 3u) { + const u8 *p = c->view[i].mem + (pc - c->view[i].base); + *w = (u32)p[0] | (u32)p[1] << 8 | (u32)p[2] << 16; + return 0; + } + r = esk_bus_region(b, pc, 3); + if (r) { + const u8 *p = r->mem + (pc - r->base); + *w = (u32)p[0] | (u32)p[1] << 8 | (u32)p[2] << 16; + return 0; + } + r = esk_bus_region(b, pc, 2); + if (r) { /* a narrow instruction at a region's end */ + const u8 *p = r->mem + (pc - r->base); + *w = (u32)p[0] | (u32)p[1] << 8; + if ((p[0] & 15u) < 8u) { + if (esk_bus_read(b, pc + 2u, 1, &hi)) + return -1; + *w |= (hi & 0xffu) << 16; + } + return 0; + } + if (esk_bus_read(b, pc & ~3u, 4, &lo)) + return -1; + if ((pc & 3u) >= 2u) { + u32 need = (pc & 3u) == 3u ? 3u : ((lo >> 16) & 15u) < 8u ? 3u : 2u; + if (need > 4u - (pc & 3u) && esk_bus_read(b, (pc & ~3u) + 4u, 4, &hi)) + return -1; + } + *w = (u32)(((u64)hi << 32 | lo) >> (8 * (pc & 3u))) & 0xffffffu; + return 0; +} + +/* ---- special registers ------------------------------------------------ */ + +static void timer_check(struct lx6 *c, u32 from, u32 cycles) +{ + int i; + for (i = 0; i < 3; i++) { + u32 d = c->ccompare[i] - from; + if (d && d <= cycles) + c->latched |= 1u << timer_int[i]; + } +} + +static void set_ccount(struct lx6 *c, u32 v) +{ + c->ccount_base = v; + c->ccount_at = now(c); +} + +/* RSR: 0, or -1 for no such register */ +static int rsr(struct lx6 *c, u32 sr, u32 *v) +{ + switch (sr) { + case 0: *v = c->lbeg; return 0; + case 1: *v = c->lend; return 0; + case 2: *v = c->lcount; return 0; + case 3: *v = c->sar; return 0; + case 4: *v = c->br; return 0; + case 12: *v = c->scompare1; return 0; + case 16: *v = c->acclo; return 0; + case 17: *v = sext(c->acchi, 8); return 0; + case 32: case 33: case 34: case 35: *v = c->mr[sr - 32]; return 0; + case 72: *v = c->wb; return 0; + case 73: *v = c->ws; return 0; + case 96: *v = c->ibreakenable; return 0; + case 97: *v = c->memctl; return 0; + case 99: *v = c->atomctl; return 0; + case 104: *v = c->ddr; return 0; + case 128: case 129: *v = c->ibreaka[sr - 128]; return 0; + case 144: case 145: *v = c->dbreaka[sr - 144]; return 0; + case 160: case 161: *v = c->dbreakc[sr - 160]; return 0; + case 176: *v = CONFIGID0; return 0; + case 208: *v = CONFIGID1; return 0; + case 192: *v = c->depc; return 0; + case 224: *v = c->cpenable; return 0; + case 226: *v = interrupt_reg(c); return 0; + case 228: *v = c->intenable; return 0; + case 230: *v = c->ps; return 0; + case 231: *v = c->vecbase; return 0; + case 232: *v = c->exccause; return 0; + case 233: *v = c->debugcause; return 0; + case 234: *v = ccount(c); return 0; + case 235: *v = c->prid; return 0; + case 236: *v = c->icount; return 0; + case 237: *v = c->icountlevel; return 0; + case 238: *v = c->excvaddr; return 0; + case 240: case 241: case 242: *v = c->ccompare[sr - 240]; return 0; + case 244: case 245: case 246: case 247: *v = c->misc[sr - 244]; return 0; + } + if (sr >= 177 && sr <= 183) { + *v = c->epc[sr - 176]; + return 0; + } + if (sr >= 194 && sr <= 199) { + *v = c->eps[sr - 192]; + return 0; + } + if (sr >= 209 && sr <= 215) { + *v = c->excsave[sr - 208]; + return 0; + } + return -1; +} + +static int wsr(struct lx6 *c, u32 sr, u32 v) +{ + switch (sr) { + case 0: c->lbeg = v; return 0; + case 1: c->lend = v; return 0; + case 2: c->lcount = v; return 0; + case 3: c->sar = v & 0x3fu; return 0; + case 4: c->br = v & 0xffffu; return 0; + case 12: c->scompare1 = v; return 0; + case 16: c->acclo = v; return 0; + case 17: c->acchi = v & 0xffu; return 0; + case 32: case 33: case 34: case 35: c->mr[sr - 32] = v; return 0; + case 72: c->wb = v & 15u; return 0; + case 73: c->ws = v & 0xffffu; return 0; + case 96: c->ibreakenable = v & 3u; return 0; + case 97: c->memctl = v; return 0; + case 99: c->atomctl = v & 0x3fu; return 0; + case 104: c->ddr = v; return 0; + case 128: case 129: c->ibreaka[sr - 128] = v; return 0; + case 144: case 145: c->dbreaka[sr - 144] = v; return 0; + case 160: case 161: c->dbreakc[sr - 160] = v & 0xc000003fu; return 0; + case 192: c->depc = v; return 0; + case 224: c->cpenable = v & 0xffu; return 0; + case 226: c->latched |= v & INT_SOFTWARE; return 0; /* INTSET */ + case 227: c->latched &= ~(v & (INT_SOFTWARE | INT_EDGE)); return 0; /* INTCLEAR */ + case 228: c->intenable = v; return 0; + case 230: c->ps = v & PS_MASK; return 0; + case 231: c->vecbase = v & ~0x3ffu; return 0; + case 232: c->exccause = v & 0x3fu; return 0; + case 233: return 0; /* DEBUGCAUSE: read-only */ + case 234: set_ccount(c, v); return 0; + case 235: return 0; /* PRID: read-only */ + case 236: c->icount = v; return 0; + case 237: c->icountlevel = v & 15u; return 0; + case 238: c->excvaddr = v; return 0; + case 240: case 241: case 242: + c->ccompare[sr - 240] = v; + c->latched &= ~(1u << timer_int[sr - 240]); + return 0; + case 244: case 245: case 246: case 247: c->misc[sr - 244] = v; return 0; + } + if (sr >= 177 && sr <= 183) { + c->epc[sr - 176] = v; + return 0; + } + if (sr >= 194 && sr <= 199) { + c->eps[sr - 192] = v & PS_MASK; + return 0; + } + if (sr >= 209 && sr <= 215) { + c->excsave[sr - 208] = v; + return 0; + } + if (sr == 176 || sr == 208) + return 0; /* CONFIGID: read-only */ + return -1; +} + +/* the user registers: THREADPTR, the FPU's FCR and FSR, and the ESP32's + * EXPSTATE and double-precision accelerator registers F64R_LO, F64R_HI, + * F64S (core-isa.h's XCHAL_HAVE_DFP_ACCEL; Espressif's xtensa-modules) */ +static u32 *dfp_ur(struct lx6 *c, u32 ur) +{ + switch (ur) { + case 230: return &c->expstate; + case 234: return &c->f64r_lo; + case 235: return &c->f64r_hi; + case 236: return &c->f64s; + } + return 0; +} + +static int rur(struct lx6 *c, u32 ur, u32 *v) +{ + if (ur == 231) { + *v = c->threadptr; + return 0; + } + if (dfp_ur(c, ur)) { + *v = *dfp_ur(c, ur); + return 0; + } + if (ur == 232 || ur == 233) { + if (!(c->cpenable & 1u)) { + exception(c, EXC_CP0_DISABLED); + return 1; + } + *v = ur == 232 ? c->fcr : c->fsr; + return 0; + } + return -1; +} + +static int wur(struct lx6 *c, u32 ur, u32 v) +{ + if (ur == 231) { + c->threadptr = v; + return 0; + } + if (dfp_ur(c, ur)) { + *dfp_ur(c, ur) = v; + return 0; + } + if (ur == 232 || ur == 233) { + if (!(c->cpenable & 1u)) { + exception(c, EXC_CP0_DISABLED); + return 1; + } + if (ur == 232) + c->fcr = v & 0x7fu; + else + c->fsr = v & 0xf80u; + return 0; + } + return -1; +} + +/* ---- the instructions ------------------------------------------------- */ + +#define OP0(w) ((w) & 15u) +#define FT(w) (((w) >> 4) & 15u) +#define FS(w) (((w) >> 8) & 15u) +#define FR(w) (((w) >> 12) & 15u) +#define OP1(w) (((w) >> 16) & 15u) +#define OP2(w) (((w) >> 20) & 15u) +#define IMM8(w) (((w) >> 16) & 255u) + +static const u32 b4const[16] = { 0xffffffffu, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 16, 32, 64, 128, 256 }; +static const u32 b4constu[16] = { 32768, 65536, 2, 3, 4, 5, 6, 7, 8, 10, 12, 16, 32, 64, 128, 256 }; + +static u32 max3(u32 a, u32 b, u32 c) +{ + u32 m = a > b ? a : b; + return m > c ? m : c; +} + +static void branch(struct lx6 *c, int taken, u32 target) +{ + if (taken) { + c->npc = target; + c->jumped = 1; + c->cost += 2; + } +} + +/* a call: return address and CALLINC, then the target */ +static void call(struct lx6 *c, u32 n, u32 target, u32 len) +{ + if (n) { + AR(c, n * 4u) = n << 30 | ((c->pc + len) & 0x3fffffffu); + c->ps = (c->ps & ~PS_CALLINC) | n << PS_CALLINC_SHIFT; + } else { + AR(c, 0) = c->pc + len; + } + c->npc = target; + c->jumped = 1; + c->cost += 2; +} + +static void retw(struct lx6 *c) +{ + u32 a0 = AR(c, 0), n = a0 >> 30, owb = c->wb, m = 0, k; + for (k = 1; k <= 3; k++) + if ((c->ws >> ((c->wb - k) & 15u)) & 1u) { + m = k; + break; + } + if (n == 0 || (m != 0 && m != n) || (c->ps & (PS_WOE | PS_EXCM)) != PS_WOE) { + esk_lx6_illegal(c); + return; + } + c->wb = (c->wb - n) & 15u; + c->cost += 2; + if ((c->ws >> c->wb) & 1u) { + c->ws &= ~(1u << owb); + c->jumped = 1; + c->npc = (c->pc & 0xc0000000u) | (a0 & 0x3fffffffu); + } else { + /* the caller's frame was spilled: the underflow handler brings + * it back, and RETW runs again */ + c->ps = (c->ps & ~PS_OWB) | owb << PS_OWB_SHIFT | PS_EXCM; + c->epc[1] = c->pc; + to(c, c->vecbase + (n == 1 ? VEC_UF4 : n == 2 ? VEC_UF8 : VEC_UF12)); + } +} + +static void entry(struct lx6 *c, u32 s, u32 imm) +{ + u32 callinc = (c->ps & PS_CALLINC) >> PS_CALLINC_SHIFT, v; + if (s > 3 || (c->ps & (PS_WOE | PS_EXCM)) != PS_WOE) { + esk_lx6_illegal(c); + return; + } + if (win(c, callinc * 4u + s)) + return; + v = AR(c, s) - imm; + AR(c, callinc * 4u + s) = v; + c->wb = (c->wb + callinc) & 15u; + c->ws |= 1u << c->wb; +} + +static void rfwo(struct lx6 *c, int underflow) +{ + if (underflow) + c->ws |= 1u << c->wb; + else + c->ws &= ~(1u << c->wb); + c->wb = (c->ps & PS_OWB) >> PS_OWB_SHIFT; + c->ps &= ~PS_EXCM; + c->npc = c->epc[1]; + c->cost += 2; +} + +static u32 nsa(u32 v) +{ + u32 n = 0; + v ^= (u32)((s32)v >> 31); + if (!v) + return 31; + while (!(v & 0x80000000u)) { + v <<= 1; + n++; + } + return n - 1u; +} + +static u32 nsau(u32 v) +{ + u32 n = 0; + if (!v) + return 32; + while (!(v & 0x80000000u)) { + v <<= 1; + n++; + } + return n; +} + +/* the external registers (RER/WER): the debug, trace and performance + * monitor blocks, which no one drives here: reads are 0 */ +static u32 rer(struct lx6 *c, u32 a) +{ + ES_UNUSED(c); + ES_UNUSED(a); + return 0; +} + +/* the MAC16 package: 16 x 16-bit products of the operands' halves into + * the 40-bit accumulator ACCHI:ACCLO (UMUL, MUL: set; MULA: add; MULS: + * subtract), the operands from AR (A) or M0-M3 (D), and the forms that + * load an M register meanwhile (LDINC, LDDEC: the address register moves + * by 4 first). op2: 0 DD.LDINC, 1 DD.LDDEC, 2 DD, 3 AD, 4 DA.LDINC, + * 5 DA.LDDEC, 6 DA, 7 AA, 8 LDINC, 9 LDDEC; op1: the operation (bits 3:2) + * and the halves (bit 0 the first operand's high half, bit 1 the + * second's) */ +static void mac16(struct lx6 *c, u32 w) +{ + u32 op2 = OP2(w), op1 = OP1(w), r = FR(w), s = FS(w), t = FT(w); + u32 x, y, oper = op1 >> 2, lda = 0, ldv = 0, hi = 0; + int ld = op2 <= 1 || op2 == 4 || op2 == 5 || op2 == 8 || op2 == 9; + int dec = op2 == 1 || op2 == 5 || op2 == 9; + int first_m = op2 <= 2 || (op2 >= 4 && op2 <= 6); + int second_m = op2 <= 3; + s64 acc, p; + if (op2 > 9 || (op2 >= 8 && (op1 || t)) || (op2 < 8 && oper == 0 && op2 != 7)) { + esk_lx6_illegal(c); + return; + } + if (op2 < 8) { + if (!first_m) + hi = s; + if (!second_m) + hi = t > hi ? t : hi; + } + if (ld) + hi = s > hi ? s : hi; + if (win(c, hi)) + return; + if (ld) { + lda = AR(c, s) + (dec ? (u32)-4 : 4u); + if (esk_lx6_load(c, lda, 4, &ldv)) + return; + } + if (op2 < 8) { + x = first_m ? c->mr[(r >> 2) & 1u] : AR(c, s); + y = second_m ? c->mr[2u + ((t >> 2) & 1u)] : AR(c, t); + x = (op1 & 1u) ? x >> 16 : x & 0xffffu; + y = (op1 & 2u) ? y >> 16 : y & 0xffffu; + if (oper == 0) + p = (s64)((u64)(x & 0xffffu) * (u64)(y & 0xffffu)); + else + p = (s64)(s32)sext(x, 16) * (s64)(s32)sext(y, 16); + acc = (s64)(((u64)(c->acchi & 0xffu) << 32 | c->acclo) << 24) >> 24; + acc = oper <= 1 ? p : oper == 2 ? acc + p : acc - p; + c->acclo = (u32)(u64)acc; + c->acchi = (u32)((u64)acc >> 32) & 0xffu; + } + if (ld) { + c->mr[r & 3u] = ldv; + AR(c, s) = lda; + } +} + +/* the ESP32's double-precision accelerator instructions (F64ITER, + * F64RND, F64ADDC, F64SUBC, F64SIG, F64CMPL, F64CMPH, F64NORM, F64SEXP, + * RF64R: Espressif's xtensa-modules): Espressif publishes their encodings, + * not what they do, so the run ends here rather than guessing */ +static void dfp(struct lx6 *c, u32 w) +{ + esk_mach_end(c->m, ESK_MACH_LOCKUP, + "core %d at 0x%08x: the double-precision accelerator's instruction 0x%06x is not modelled", + c->id, (unsigned)c->pc, (unsigned)w); + c->exc = 1; + c->npc = c->pc; +} + +static int cp0(struct lx6 *c) +{ + if (c->cpenable & 1u) + return 0; + exception(c, EXC_CP0_DISABLED); + return 1; +} + +/* RST0's ST0: the calls through a register, returns, syncs, the returns + * from exceptions, BREAK, SYSCALL, RSIL, WAITI and the boolean groups */ +static void st0(struct lx6 *c, u32 w) +{ + u32 r = FR(w), s = FS(w), t = FT(w), v; + switch (r) { + case 0: { /* SNM0 */ + u32 m = t >> 2, n = t & 3u; + if (m == 0 && n == 0) { + esk_lx6_illegal(c); + } else if (m == 2 && n == 0) { /* RET */ + if (win(c, 0)) + return; + c->npc = AR(c, 0); + c->jumped = 1; + c->cost += 2; + } else if (m == 2 && n == 1) { /* RETW */ + retw(c); + } else if (m == 2 && n == 2) { /* JX */ + if (win(c, s)) + return; + c->npc = AR(c, s); + c->jumped = 1; + c->cost += 2; + } else if (m == 3) { /* CALLX0, 4, 8, 12 */ + if (win(c, s > n * 4u ? s : n * 4u)) + return; + v = AR(c, s); + call(c, n, v, 3); + } else { + esk_lx6_illegal(c); + } + return; + } + case 1: /* MOVSP at, as */ + if (win(c, s > t ? s : t)) + return; + if (!(((c->ws >> ((c->wb - 1u) & 15u)) | (c->ws >> ((c->wb - 2u) & 15u)) | + (c->ws >> ((c->wb - 3u) & 15u))) & 1u)) { + exception(c, EXC_ALLOCA); + return; + } + AR(c, t) = AR(c, s); + return; + case 2: /* SYNC: ISYNC RSYNC ESYNC DSYNC EXCW MEMW EXTW NOP */ + if (s || (t > 3 && t != 8 && t != 12 && t != 13 && t != 15)) + esk_lx6_illegal(c); + return; + case 3: /* RFEI */ + if (t == 0) { + switch (s) { + case 0: /* RFE */ + c->ps &= ~PS_EXCM; + c->npc = c->epc[1]; + c->cost += 2; + esk_mach_exc_return(c->m, c->id); + return; + case 1: /* RFUE: XEA1 only */ + esk_lx6_illegal(c); + return; + case 2: /* RFDE */ + c->npc = c->depc; + c->cost += 2; + esk_mach_exc_return(c->m, c->id); + return; + case 4: rfwo(c, 0); return; + case 5: rfwo(c, 1); return; + } + } else if (t == 1 && s >= 2 && s <= 7) { /* RFI level */ + c->ps = c->eps[s]; + c->npc = c->epc[s]; + c->cost += 2; + esk_mach_exc_return(c->m, c->id); + return; + } + esk_lx6_illegal(c); + return; + case 4: /* BREAK s, t */ + debug_exception(c, DBG_BREAK); + return; + case 5: /* SYSCALL, SIMCALL (no simulator: nothing) */ + if (t == 0 && s == 0) + exception(c, EXC_SYSCALL); + else if (!(t == 0 && s == 1)) + esk_lx6_illegal(c); + return; + case 6: /* RSIL at, level */ + if (win(c, t)) + return; + AR(c, t) = c->ps; + c->ps = (c->ps & ~PS_INTLEVEL) | s; + return; + case 7: /* WAITI */ + c->ps = (c->ps & ~PS_INTLEVEL) | s; + c->waiti = 1; + return; + case 8: case 9: case 10: case 11: { /* ANY4 ALL4 ANY8 ALL8 */ + u32 width = r < 10 ? 4u : 8u, mask = (1u << width) - 1u, bits; + if (s & (width - 1u)) { + esk_lx6_illegal(c); + return; + } + bits = (c->br >> s) & mask; + v = (r & 1u) ? bits == mask : bits != 0; + c->br = (c->br & ~(1u << t)) | v << t; + return; + } + } + esk_lx6_illegal(c); +} + +/* region protection's TLB instructions (RST0's op2 5) */ +static void tlb(struct lx6 *c, u32 w) +{ + u32 r = FR(w), s = FS(w), t = FT(w), a, *tl; + if (win(c, s > t ? s : t)) + return; + a = AR(c, s); + tl = r < 8 ? c->itlb : c->dtlb; + switch (r & 7u) { + case 3: AR(c, t) = a & 0xe0000000u; return; /* R?TLB0 */ + case 4: return; /* I?TLB */ + case 5: AR(c, t) = (a & 0xe0000000u) | 1u; return; /* P?TLB */ + case 6: tl[a >> 29] = AR(c, t) & 15u; return; /* W?TLB */ + case 7: AR(c, t) = (a & 0xe0000000u) | tl[a >> 29]; return; /* R?TLB1 */ + } + esk_lx6_illegal(c); +} + +/* one instruction `w` of `len` bytes at c->pc */ +static void exec(struct lx6 *c, u32 w, u32 len) +{ + u32 op0 = OP0(w), r = FR(w), s = FS(w), t = FT(w), op1 = OP1(w), op2 = OP2(w); + u32 a, v, x, y; + switch (op0) { + case 0: /* QRST */ + switch (op1) { + case 0: /* RST0 */ + switch (op2) { + case 0: st0(c, w); return; + case 1: case 2: case 3: /* AND OR XOR */ + if (win(c, max3(r, s, t))) + return; + x = AR(c, s); + y = AR(c, t); + AR(c, r) = op2 == 1 ? x & y : op2 == 2 ? x | y : x ^ y; + return; + case 4: /* ST1 */ + switch (r) { + case 0: case 1: case 2: case 3: + if (win(c, s)) + return; + x = AR(c, s); + c->sar = r == 0 ? x & 31u : r == 1 ? 32u - (x & 31u) : r == 2 ? (x & 3u) * 8u + : 32u - (x & 3u) * 8u; + return; + case 4: /* SSAI */ + c->sar = s | (t & 1u) << 4; + return; + case 6: /* RER */ + if (win(c, s > t ? s : t)) + return; + AR(c, t) = rer(c, AR(c, s)); + return; + case 7: /* WER */ + if (win(c, s > t ? s : t)) + return; + return; + case 8: /* ROTW */ + c->wb = (c->wb + t) & 15u; + return; + case 14: case 15: /* NSA NSAU */ + if (win(c, s > t ? s : t)) + return; + AR(c, t) = r == 14 ? nsa(AR(c, s)) : nsau(AR(c, s)); + return; + } + break; + case 5: tlb(c, w); return; + case 6: /* NEG ABS */ + if (s > 1) + break; + if (win(c, r > t ? r : t)) + return; + x = AR(c, t); + AR(c, r) = s == 0 ? 0u - x : (x & 0x80000000u) ? 0u - x : x; + return; + case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: + if (win(c, max3(r, s, t))) + return; + x = AR(c, s) << (op2 & 3u); + y = AR(c, t); + AR(c, r) = op2 < 12 ? x + y : x - y; + return; + } + break; + case 1: /* RST1 */ + switch (op2) { + case 0: case 1: /* SLLI */ + if (win(c, r > s ? r : s)) + return; + AR(c, r) = (u32)((u64)AR(c, s) << (32u - (t | (op2 & 1u) << 4))); + return; + case 2: case 3: /* SRAI */ + if (win(c, r > t ? r : t)) + return; + AR(c, r) = (u32)((s32)AR(c, t) >> (s | (op2 & 1u) << 4)); + return; + case 4: /* SRLI */ + if (win(c, r > t ? r : t)) + return; + AR(c, r) = AR(c, t) >> s; + return; + case 6: /* XSR */ + if (win(c, t)) + return; + if (rsr(c, r << 4 | s, &v)) { + esk_lx6_illegal(c); + return; + } + x = AR(c, t); + if (wsr(c, r << 4 | s, x)) { + esk_lx6_illegal(c); + return; + } + AR(c, t) = v; + return; + case 8: /* SRC */ + if (win(c, max3(r, s, t))) + return; + AR(c, r) = (u32)(((u64)AR(c, s) << 32 | AR(c, t)) >> c->sar); + return; + case 9: /* SRL */ + if (win(c, r > t ? r : t)) + return; + AR(c, r) = (u32)((u64)AR(c, t) >> c->sar); + return; + case 10: /* SLL */ + if (win(c, r > s ? r : s)) + return; + AR(c, r) = (u32)(((u64)AR(c, s) << 32) >> c->sar); + return; + case 11: /* SRA */ + if (win(c, r > t ? r : t)) + return; + AR(c, r) = (u32)((s64)(s32)AR(c, t) >> (c->sar > 63u ? 63u : c->sar)); + return; + case 12: /* MUL16U */ + if (win(c, max3(r, s, t))) + return; + AR(c, r) = (AR(c, s) & 0xffffu) * (AR(c, t) & 0xffffu); + return; + case 13: /* MUL16S */ + if (win(c, max3(r, s, t))) + return; + AR(c, r) = (u32)((s32)sext(AR(c, s), 16) * (s32)sext(AR(c, t), 16)); + return; + } + break; + case 2: /* RST2 */ + if (op2 <= 4) { /* ANDB ANDBC ORB ORBC XORB */ + u32 bs = (c->br >> s) & 1u, bt = (c->br >> t) & 1u, b; + b = op2 == 0 ? bs & bt : op2 == 1 ? bs & !bt : op2 == 2 ? bs | bt : op2 == 3 ? bs | !bt + : bs ^ bt; + c->br = (c->br & ~(1u << r)) | b << r; + return; + } + if (op2 < 8 || op2 == 9) + break; + if (win(c, max3(r, s, t))) + return; + x = AR(c, s); + y = AR(c, t); + switch (op2) { + case 8: AR(c, r) = x * y; c->cost += 1; return; /* MULL */ + case 10: AR(c, r) = (u32)(((u64)x * y) >> 32); c->cost += 1; return; /* MULUH */ + case 11: AR(c, r) = (u32)((u64)((s64)(s32)x * (s32)y) >> 32); c->cost += 1; return; /* MULSH */ + } + if (!y) { + exception(c, EXC_DIVZERO); + return; + } + c->cost += 11; + switch (op2) { + case 12: AR(c, r) = x / y; return; /* QUOU */ + case 13: /* QUOS */ + AR(c, r) = (x == 0x80000000u && y == 0xffffffffu) ? x : (u32)((s32)x / (s32)y); + return; + case 14: AR(c, r) = x % y; return; /* REMU */ + case 15: /* REMS */ + AR(c, r) = (x == 0x80000000u && y == 0xffffffffu) ? 0u : (u32)((s32)x % (s32)y); + return; + } + break; + case 3: /* RST3 */ + switch (op2) { + case 0: /* RSR */ + if (win(c, t)) + return; + if (rsr(c, r << 4 | s, &v)) { + esk_lx6_illegal(c); + return; + } + AR(c, t) = v; + return; + case 1: /* WSR */ + if (win(c, t)) + return; + if (wsr(c, r << 4 | s, AR(c, t))) + esk_lx6_illegal(c); + return; + case 2: /* SEXT */ + if (win(c, r > s ? r : s)) + return; + AR(c, r) = sext(AR(c, s), (int)t + 8); + return; + case 3: { /* CLAMPS */ + s32 lim = (s32)(1u << (t + 7u)), z; + if (win(c, r > s ? r : s)) + return; + z = (s32)AR(c, s); + AR(c, r) = (u32)(z < -lim ? -lim : z > lim - 1 ? lim - 1 : z); + return; + } + case 4: case 5: case 6: case 7: /* MIN MAX MINU MAXU */ + if (win(c, max3(r, s, t))) + return; + x = AR(c, s); + y = AR(c, t); + if (op2 == 4) + AR(c, r) = (s32)x < (s32)y ? x : y; + else if (op2 == 5) + AR(c, r) = (s32)x < (s32)y ? y : x; + else if (op2 == 6) + AR(c, r) = x < y ? x : y; + else + AR(c, r) = x < y ? y : x; + return; + case 8: case 9: case 10: case 11: { /* MOVEQZ MOVNEZ MOVLTZ MOVGEZ */ + int mv; + if (win(c, max3(r, s, t))) + return; + y = AR(c, t); + mv = op2 == 8 ? y == 0 : op2 == 9 ? y != 0 : op2 == 10 ? (s32)y < 0 : (s32)y >= 0; + if (mv) + AR(c, r) = AR(c, s); + return; + } + case 12: case 13: /* MOVF MOVT */ + if (win(c, r > s ? r : s)) + return; + if (((c->br >> t) & 1u) == (op2 == 13)) + AR(c, r) = AR(c, s); + return; + case 14: { /* RUR */ + int k; + if (win(c, r)) + return; + k = rur(c, s << 4 | t, &v); + if (k < 0) + esk_lx6_illegal(c); + else if (k == 0) + AR(c, r) = v; + return; + } + case 15: /* WUR */ + if (win(c, t)) + return; + if (wur(c, r << 4 | s, AR(c, t)) < 0) + esk_lx6_illegal(c); + return; + } + break; + case 4: case 5: /* EXTUI */ + if (win(c, r > t ? r : t)) + return; + x = AR(c, t) >> (s | (op1 & 1u) << 4); + AR(c, r) = x & (u32)((1ull << (op2 + 1u)) - 1u); + return; + case 8: case 10: case 11: /* LSCX, FP0, FP1 */ + if (op1 == 11 && (op2 == 0 || op2 == 14 || op2 == 15)) { + dfp(c, w); /* F64ADDC, F64SUBC, F64CMPL, RF64R */ + return; + } + if (cp0(c)) + return; + if (esk_lx6_fp(c, w) < 0) + esk_lx6_illegal(c); + return; + case 14: case 15: /* the double-precision accelerator's other instructions */ + if ((op1 == 14 && op2 <= 8) || (op1 == 15 && op2 == 0)) { + dfp(c, w); + return; + } + break; + case 9: /* LSC4: L32E S32E */ + if (op2 != 0 && op2 != 4) + break; + if (win(c, s > t ? s : t)) + return; + a = AR(c, s) + ((r << 2) | 0xffffffc0u); + if (op2 == 0) { + if (esk_lx6_load(c, a, 4, &v) == 0) + AR(c, t) = v; + } else { + (void)esk_lx6_store(c, a, 4, AR(c, t)); + } + return; + } + esk_lx6_illegal(c); + return; + case 1: /* L32R */ + if (win(c, t)) + return; + a = ((c->pc + 3u) & ~3u) + ((0xffff0000u | (w >> 8)) << 2); + if (esk_lx6_load(c, a, 4, &v) == 0) + AR(c, t) = v; + return; + case 2: { /* LSAI */ + u32 imm = IMM8(w); + switch (r) { + case 0: case 1: case 2: case 9: case 11: { /* L8UI L16UI L32I L16SI L32AI */ + int n = r == 0 ? 1 : r == 1 || r == 9 ? 2 : 4; + if (win(c, s > t ? s : t)) + return; + a = AR(c, s) + imm * (u32)n; + if (esk_lx6_load(c, a, n, &v)) + return; + AR(c, t) = r == 9 ? sext(v, 16) : v; + return; + } + case 4: case 5: case 6: case 15: { /* S8I S16I S32I S32RI */ + int n = r == 4 ? 1 : r == 5 ? 2 : 4; + if (win(c, s > t ? s : t)) + return; + a = AR(c, s) + imm * (u32)n; + (void)esk_lx6_store(c, a, n, AR(c, t)); + return; + } + case 7: /* CACHE: no caches, nothing to do (DPFR, IPF, ...) */ + if (win(c, s)) + return; + return; + case 10: /* MOVI */ + if (win(c, t)) + return; + AR(c, t) = sext(imm | s << 8, 12); + return; + case 12: /* ADDI */ + if (win(c, s > t ? s : t)) + return; + AR(c, t) = AR(c, s) + sext(imm, 8); + return; + case 13: /* ADDMI */ + if (win(c, s > t ? s : t)) + return; + AR(c, t) = AR(c, s) + (sext(imm, 8) << 8); + return; + case 14: /* S32C1I */ + if (win(c, s > t ? s : t)) + return; + a = AR(c, s) + imm * 4u; + if (esk_lx6_load(c, a, 4, &v)) + return; + if (v == c->scompare1 && esk_lx6_store(c, a, 4, AR(c, t))) + return; + AR(c, t) = v; + return; + } + esk_lx6_illegal(c); + return; + } + case 3: /* LSCI: LSI SSI (LSIU, SSIU are not in this configuration) */ + if (r != 0 && r != 4) { + esk_lx6_illegal(c); + return; + } + if (cp0(c)) + return; + if (win(c, s)) + return; + a = AR(c, s) + IMM8(w) * 4u; + if (r == 0) { + if (esk_lx6_load(c, a, 4, &v) == 0) + c->f[t] = v; + } else { + (void)esk_lx6_store(c, a, 4, c->f[t]); + } + return; + case 4: /* MAC16 */ + mac16(c, w); + return; + case 5: { /* CALLN */ + u32 n = (w >> 4) & 3u; + if (win(c, n * 4u)) + return; + call(c, n, (c->pc & ~3u) + 4u + (sext(w >> 6, 18) << 2), 3); + return; + } + case 6: { /* SI */ + u32 n = (w >> 4) & 3u, m = (w >> 6) & 3u; + if (n == 0) { /* J */ + c->npc = c->pc + 4u + sext(w >> 6, 18); + c->jumped = 1; + c->cost += 2; + return; + } + if (n == 1) { /* BZ */ + if (win(c, s)) + return; + x = AR(c, s); + branch(c, m == 0 ? x == 0 : m == 1 ? x != 0 : m == 2 ? (s32)x < 0 : (s32)x >= 0, + c->pc + 4u + sext(w >> 12, 12)); + return; + } + if (n == 2) { /* BI0 */ + if (win(c, s)) + return; + x = AR(c, s); + y = b4const[r]; + branch(c, m == 0 ? x == y : m == 1 ? x != y : m == 2 ? (s32)x < (s32)y : (s32)x >= (s32)y, + c->pc + 4u + sext(IMM8(w), 8)); + return; + } + /* BI1 */ + if (m == 0) { /* ENTRY */ + entry(c, s, (w >> 12) << 3); + return; + } + if (m == 1) { /* B1 */ + switch (r) { + case 0: case 1: /* BF BT */ + branch(c, ((c->br >> s) & 1u) == r, c->pc + 4u + sext(IMM8(w), 8)); + return; + case 8: case 9: case 10: /* LOOP LOOPNEZ LOOPGTZ */ + if (win(c, s)) + return; + x = AR(c, s); + c->lcount = x - 1u; + c->lbeg = c->pc + 3u; + c->lend = c->pc + 4u + IMM8(w); + if ((r == 9 && x == 0) || (r == 10 && (s32)x <= 0)) { + c->npc = c->lend; + c->jumped = 1; + c->cost += 2; + } + return; + } + esk_lx6_illegal(c); + return; + } + if (win(c, s)) + return; + x = AR(c, s); + y = b4constu[r]; + branch(c, m == 2 ? x < y : x >= y, c->pc + 4u + sext(IMM8(w), 8)); /* BLTUI BGEUI */ + return; + } + case 7: { /* B */ + int taken; + u32 bit; + if (win(c, (r & 7u) == 6u || (r & 7u) == 7u ? s : s > t ? s : t)) + return; + x = AR(c, s); + switch (r) { + case 0: taken = (x & AR(c, t)) == 0; break; /* BNONE */ + case 1: taken = x == AR(c, t); break; /* BEQ */ + case 2: taken = (s32)x < (s32)AR(c, t); break; /* BLT */ + case 3: taken = x < AR(c, t); break; /* BLTU */ + case 4: taken = (~x & AR(c, t)) == 0; break; /* BALL */ + case 5: taken = !((x >> (AR(c, t) & 31u)) & 1u); break; /* BBC */ + case 6: case 7: bit = t | (r & 1u) << 4; taken = !((x >> bit) & 1u); break; /* BBCI */ + case 8: taken = (x & AR(c, t)) != 0; break; /* BANY */ + case 9: taken = x != AR(c, t); break; /* BNE */ + case 10: taken = (s32)x >= (s32)AR(c, t); break; /* BGE */ + case 11: taken = x >= AR(c, t); break; /* BGEU */ + case 12: taken = (~x & AR(c, t)) != 0; break; /* BNALL */ + case 13: taken = (x >> (AR(c, t) & 31u)) & 1u; break; /* BBS */ + default: bit = t | (r & 1u) << 4; taken = (x >> bit) & 1u; break; /* BBSI */ + } + branch(c, taken, c->pc + 4u + sext(IMM8(w), 8)); + return; + } + case 8: /* L32I.N */ + if (win(c, s > t ? s : t)) + return; + if (esk_lx6_load(c, AR(c, s) + r * 4u, 4, &v) == 0) + AR(c, t) = v; + return; + case 9: /* S32I.N */ + if (win(c, s > t ? s : t)) + return; + (void)esk_lx6_store(c, AR(c, s) + r * 4u, 4, AR(c, t)); + return; + case 10: /* ADD.N */ + if (win(c, max3(r, s, t))) + return; + AR(c, r) = AR(c, s) + AR(c, t); + return; + case 11: /* ADDI.N */ + if (win(c, r > s ? r : s)) + return; + AR(c, r) = AR(c, s) + (t ? t : 0xffffffffu); + return; + case 12: /* ST2: MOVI.N BEQZ.N BNEZ.N */ + if (win(c, s)) + return; + if (!(t & 8u)) { + v = r | (t & 7u) << 4; + AR(c, s) = v >= 96u ? v - 128u : v; + } else { + x = AR(c, s); + branch(c, (t & 4u) ? x != 0 : x == 0, c->pc + 4u + (r | (t & 3u) << 4)); + } + return; + case 13: /* ST3 */ + if (r == 0) { /* MOV.N */ + if (win(c, s > t ? s : t)) + return; + AR(c, t) = AR(c, s); + return; + } + if (r == 15) { + switch (t) { + case 0: /* RET.N */ + c->npc = AR(c, 0); + c->jumped = 1; + c->cost += 2; + return; + case 1: retw(c); return; + case 2: debug_exception(c, DBG_BREAKN); return; + case 3: return; /* NOP.N */ + } + } + esk_lx6_illegal(c); + return; + } + ES_UNUSED(len); + esk_lx6_illegal(c); +} + +#ifdef LX6_DEBUG_TRACE +#define RING 65536 +static char ring[RING][96]; +static unsigned ring_n; +static FILE *ring_f; +static void ring_dump(void) +{ + unsigned i, n = ring_n < RING ? ring_n : RING; + for (i = 0; i < n; i++) + fputs(ring[(ring_n - n + i) % RING], ring_f); + fclose(ring_f); +} +static void lx6_debug_trace(struct lx6 *c, u32 w) +{ + static int opened; + if (!opened) { + const char *e = getenv("LX6_TRACE"); + opened = 1; + ring_f = e ? fopen(e, "w") : 0; + if (ring_f) + atexit(ring_dump); + } + { + static u32 stop_pc; + static int got; + if (!got) { + const char *e = getenv("LX6_STOP_PC"); + got = 1; + stop_pc = e ? (u32)strtoul(e, 0, 16) : 0; + } + { + static u32 logs[32]; + static int nlogs = -1; + int q; + if (nlogs < 0) { + const char *e = getenv("LX6_LOG_PCS"); + nlogs = 0; + while (e && *e && nlogs < 32) { + char *end; + logs[nlogs++] = (u32)strtoul(e, &end, 16); + e = *end ? end + 1 : end; + } + } + for (q = 0; q < nlogs; q++) + if (c->pc == logs[q]) { + char str[40]; + int z; + for (z = 0; z < 39; z++) { + u32 ch = 0; + if (esk_bus_debug_read(&c->m->bus, AR(c, 3) + (u32)z, 1, &ch) || ch < 32 || ch > 126) + break; + str[z] = (char)ch; + } + str[z] = 0; + fprintf(stderr, "PCLOG core %d at %08x cycles %llu a0=%08x a2=%08x a3=%08x a4=%08x s3=%s\n", c->id, + (unsigned)c->pc, (unsigned long long)c->m->cycles, (unsigned)AR(c, 0), (unsigned)AR(c, 2), + (unsigned)AR(c, 3), (unsigned)AR(c, 4), str); + } + } + if (stop_pc && c->pc == stop_pc) { + fprintf(stderr, "LX6_STOP_PC hit on core %d\n", c->id); + exit(0); + } + } + if (ring_f) + snprintf(ring[ring_n++ % RING], 96, "%d %08x %06x a0=%08x a1=%08x a2=%08x a3=%08x\n", c->id, + (unsigned)c->pc, (unsigned)w, (unsigned)AR(c, 0), (unsigned)AR(c, 1), (unsigned)AR(c, 2), + (unsigned)AR(c, 3)); +} +#endif + +/* ---- interrupts and a step -------------------------------------------- */ + +/* the level of the interrupt the core would take now, or 0 */ +static u32 irq_level(const struct lx6 *c) +{ + u32 pend = interrupt_reg(c) & c->intenable, th = c->ps & PS_INTLEVEL, l; + if (!pend) + return 0; + if ((c->ps & PS_EXCM) && th < EXCM_LEVEL) + th = EXCM_LEVEL; + if (pend & level_mask[7]) + return 7; + for (l = 5; l > th; l--) + if (pend & level_mask[l]) + return l; + return 0; +} + +static int take_irq(struct lx6 *c) +{ + u32 l = irq_level(c); + if (!l) + return 0; + if (l == 1) + exception(c, EXC_LEVEL1); + else + level_entry(c, l, c->pc); + if (l == 7) + c->latched &= ~INT_NMI; + if (l != 1) + esk_mach_exc(c->m, c->id, 64 + (int)l, c->pc); + c->waiti = 0; + return 1; +} + +static void core_step(struct lx6 *c) +{ + struct esk_mach *m = c->m; + u32 w = 0, len = 3, before = ccount(c); + c->exc = 0; + c->cost = 1; + if (take_irq(c)) + goto done; + if (c->waiti) { + m->asleep = 1; + return; + } + if (c->ibreakenable && (c->ps & PS_INTLEVEL) < DEBUG_LEVEL && + (((c->ibreakenable & 1u) && c->pc == c->ibreaka[0]) || + ((c->ibreakenable & 2u) && c->pc == c->ibreaka[1]))) { + c->debugcause = DBG_IBREAK; + level_entry(c, DEBUG_LEVEL, c->pc); + goto done; + } + if (prohibited(c->itlb[c->pc >> 29])) { + c->excvaddr = c->pc; + exception(c, EXC_IFETCH_PROHIBITED); + goto done; + } + if (fetch(c, c->pc, &w)) { + if (c->pc == c->vecbase + VEC_KERNEL || c->pc == c->vecbase + VEC_DOUBLE) { + esk_mach_end(m, ESK_MACH_LOCKUP, "core %d: the exception vector 0x%08x cannot be fetched", + c->id, (unsigned)c->pc); + return; + } + esk_mach_bus_error(m, c->pc, 3, 0); + c->excvaddr = c->pc; + exception(c, EXC_IFETCH_ADDR); + goto done; + } + if ((w & 15u) >= 8u) { + len = 2; + w &= 0xffffu; + } + c->npc = c->pc + len; + c->jumped = 0; + m->insns++; +#ifdef LX6_DEBUG_TRACE + lx6_debug_trace(c, w); +#endif + if ((w & 15u) >= 14u) + esk_lx6_illegal(c); + else + exec(c, w, len); + if (c->exc == 3) { /* the core was reset by its own store */ + c->exc = 0; + m->cycles += c->cost; + return; + } + if (c->exc == 2) { /* a watchpoint: the debugger stops before it */ + m->insns--; + c->exc = 0; + return; + } + if (!c->exc) { + if (!c->jumped && c->npc == c->pc + len && c->npc == c->lend && c->lcount && !(c->ps & PS_EXCM)) { + c->lcount--; + c->npc = c->lbeg; + } + if (c->icountlevel > (c->ps & PS_INTLEVEL) && ++c->icount == 0) { + c->pc = c->npc; + c->debugcause = DBG_ICOUNT; + level_entry(c, DEBUG_LEVEL, c->pc); + } + } +done: + c->pc = c->npc; + m->cycles += c->cost; + timer_check(c, before, c->cost); +} + +static int core_wake_pending(const struct lx6 *c) +{ + return irq_level(c) != 0; +} + +static void core_reset(struct lx6 *c) +{ + int i; + u32 prid = c->prid, rv = c->reset_vector; + memset(c->ar, 0, sizeof c->ar); + c->pc = c->npc = rv ? 0x40000400u : 0x50000000u; + c->wb = 0; + c->ws = 1; + c->ps = 0x1fu; /* INTLEVEL 15, EXCM */ + c->sar = c->lbeg = c->lend = c->lcount = c->br = c->scompare1 = 0; + c->acclo = c->acchi = 0; + memset(c->mr, 0, sizeof c->mr); + memset(c->epc, 0, sizeof c->epc); + memset(c->eps, 0, sizeof c->eps); + memset(c->excsave, 0, sizeof c->excsave); + c->depc = c->exccause = c->excvaddr = c->debugcause = 0; + c->intenable = c->latched = 0; + c->vecbase = 0x40000000u; + memset(c->ccompare, 0, sizeof c->ccompare); + c->ccount_base = 0; + c->ccount_at = c->m->cycles; + c->cpenable = c->threadptr = c->fcr = c->fsr = 0; + c->expstate = c->f64r_lo = c->f64r_hi = c->f64s = 0; + memset(c->f, 0, sizeof c->f); + memset(c->misc, 0, sizeof c->misc); + c->prid = prid; + c->atomctl = 0x28u; + c->memctl = 1u; + c->ibreakenable = c->icount = c->icountlevel = c->ddr = 0; + memset(c->ibreaka, 0, sizeof c->ibreaka); + memset(c->dbreaka, 0, sizeof c->dbreaka); + memset(c->dbreakc, 0, sizeof c->dbreakc); + for (i = 0; i < 8; i++) + c->itlb[i] = c->dtlb[i] = 2u; + c->waiti = 0; + c->exc = 0; +} + +/* ---- two cores, one interface ----------------------------------------- */ + +static void smp_step(struct esk_cpu *cpu); + +static struct smp *smp_of(struct esk_mach *m) +{ + return m->cpu && m->cpu->ops->step == smp_step ? (struct smp *)m->cpu : 0; +} + +static int next_core(const struct smp *x) +{ + int i, best = -1; + for (i = 0; i < x->n; i++) + if (x->state[i] == CORE_RUN && (best < 0 || x->lt[i] < x->lt[best])) + best = i; + return best < 0 ? 0 : best; +} + +static int current(const struct smp *x) +{ + return x->stepping ? x->cur : next_core(x); +} + +static int any_running(const struct smp *x) +{ + int i; + for (i = 0; i < x->n; i++) + if (x->state[i] == CORE_RUN) + return 1; + return 0; +} + +static void settle(struct smp *x, int i) +{ + int st = x->held[i] ? CORE_HELD : x->stalled[i] ? CORE_STALLED + : x->c[i]->waiti && !core_wake_pending(x->c[i]) ? CORE_SLEEP : CORE_RUN; + if (st == CORE_RUN && x->state[i] != CORE_RUN) { + if (x->lt[i] < x->m->cycles) + x->lt[i] = x->m->cycles; + x->m->asleep = 0; + } + x->state[i] = st; +} + +static void smp_reset(struct esk_cpu *cpu) +{ + struct smp *x = (struct smp *)cpu; + int i; + for (i = 0; i < x->n; i++) { + core_reset(x->c[i]); + x->held[i] = i != 0; /* APP_CPU: DPORT holds it until released */ + x->stalled[i] = 0; + x->lt[i] = x->m->cycles; + x->state[i] = i == 0 ? CORE_RUN : CORE_HELD; + } + x->stepping = 0; + x->cur = 0; +} + +static void smp_step(struct esk_cpu *cpu) +{ + struct smp *x = (struct smp *)cpu; + struct esk_mach *m = x->m; + u64 g0 = m->cycles, g = ~(u64)0; + int i = next_core(x), counting = m->counting, j; + if (x->state[i] != CORE_RUN) { + m->asleep = 1; + return; + } + m->cycles = x->lt[i]; + m->counting = 0; + m->asleep = 0; + x->cur = i; + x->stepping = 1; + esk_mach_step_core(m, i, &x->c[i]->cpu); + x->stepping = 0; + x->lt[i] = m->cycles; + m->counting += counting; + if (x->c[i]->waiti && x->state[i] == CORE_RUN && !core_wake_pending(x->c[i])) + x->state[i] = CORE_SLEEP; + for (j = 0; j < x->n; j++) + if (x->state[j] == CORE_RUN && x->lt[j] < g) + g = x->lt[j]; + if (g == ~(u64)0) + g = x->lt[i] > g0 ? x->lt[i] : g0; + m->cycles = g0; + if (g > g0) { + m->cycles = g; + if (m->counting) + esk_mach_advance(m, (u32)(g - g0)); + } + m->asleep = !any_running(x); +} + +/* the core interface on one core: the analyses step it */ +static void one_step(struct esk_cpu *cpu) +{ + core_step((struct lx6 *)cpu); +} + +/* the CPU timers of the cores that wait: they wake at CCOMPARE */ +static void tm_tick(void *ctx, u32 cycles) +{ + struct smp *x = (struct smp *)ctx; + int i; + for (i = 0; i < x->n; i++) { + struct lx6 *c = x->c[i]; + if (x->state[i] != CORE_SLEEP) + continue; + timer_check(c, ccount(c) - cycles, cycles); + if (core_wake_pending(c)) + settle(x, i); + } +} + +static int tm_next(void *ctx, u32 *cycles) +{ + struct smp *x = (struct smp *)ctx; + int i, k, any = 0; + for (i = 0; i < x->n; i++) { + struct lx6 *c = x->c[i]; + if (x->state[i] != CORE_SLEEP) + continue; + for (k = 0; k < 3; k++) { + u32 d = c->ccompare[k] - ccount(c); + if (!(c->intenable & (1u << timer_int[k])) || (c->latched & (1u << timer_int[k]))) + continue; + if (!d) + d = 0xffffffffu; + if (!any || d < *cycles) + *cycles = d; + any = 1; + } + } + return any; +} + +static void tm_reset(void *ctx) +{ + ES_UNUSED(ctx); +} + +static const struct esk_dev_ops timer_ops = { + "lx6-timers", 0, 0, tm_reset, tm_tick, tm_next, +}; + +/* ---- registers, as GDB numbers them ----------------------------------- + * + * Espressif's xtensa-esp32-elf-gdb: pc 0, the 64 physical ARs 1-64, lbeg, + * lend, lcount, sar, windowbase, windowstart, configid0, configid1, ps, + * threadptr, br, scompare1, acclo, acchi, m0-m3, expstate, f64r_lo, + * f64r_hi, f64s, f0-f15, fcr, fsr (65-104). The window's a0-a15 follow + * here at 105-120 for a register view. */ + +enum { GR_PC = 0, GR_AR = 1, GR_LBEG = 65, GR_F = 87, GR_FCR = 103, GR_FSR = 104, GR_A = 105, GR_N = 121 }; + +static int put(u8 *b, u32 v) +{ + b[0] = (u8)v; + b[1] = (u8)(v >> 8); + b[2] = (u8)(v >> 16); + b[3] = (u8)(v >> 24); + return 4; +} + +static u32 get(const u8 *b) +{ + return (u32)b[0] | (u32)b[1] << 8 | (u32)b[2] << 16 | (u32)b[3] << 24; +} + +static u32 *reg_ptr(struct lx6 *c, int n) +{ + static u32 zero; + if (n == GR_PC) + return &c->pc; + if (n >= GR_AR && n < GR_AR + 64) + return &c->ar[n - GR_AR]; + if (n >= GR_A && n < GR_A + 16) + return &AR(c, n - GR_A); + if (n >= GR_F && n < GR_F + 16) + return &c->f[n - GR_F]; + switch (n) { + case 65: return &c->lbeg; + case 66: return &c->lend; + case 67: return &c->lcount; + case 68: return &c->sar; + case 69: return &c->wb; + case 70: return &c->ws; + case 73: return &c->ps; + case 74: return &c->threadptr; + case 75: return &c->br; + case 76: return &c->scompare1; + case 77: return &c->acclo; + case 78: return &c->acchi; + case 79: case 80: case 81: case 82: return &c->mr[n - 79]; + case GR_FCR: return &c->fcr; + case GR_FSR: return &c->fsr; + } + zero = 0; + return n == 71 || n == 72 || (n >= 83 && n <= 86) ? &zero : 0; +} + +static int core_reg_read(struct lx6 *c, int n, u8 *buf) +{ + u32 *p; + if (n == 71) + return put(buf, CONFIGID0); + if (n == 72) + return put(buf, CONFIGID1); + p = reg_ptr(c, n); + return p ? put(buf, *p) : 0; +} + +static int core_reg_write(struct lx6 *c, int n, const u8 *buf) +{ + u32 *p = n == 71 || n == 72 ? 0 : reg_ptr(c, n); + if (!p) + return 0; + *p = get(buf); + c->wb &= 15u; + c->ws &= 0xffffu; + c->ps &= PS_MASK; + if (n == GR_PC) + c->npc = c->pc; + return 4; +} + +static const char *core_reg_name(int n) +{ + static const char *const a[16] = { "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", + "a8", "a9", "a10", "a11", "a12", "a13", "a14", "a15" }; + static const char *const sr[] = { "lbeg", "lend", "lcount", "sar", "windowbase", "windowstart", + "configid0", "configid1", "ps", "threadptr", "br", "scompare1", + "acclo", "acchi", "m0", "m1", "m2", "m3", "expstate", + "f64r_lo", "f64r_hi", "f64s" }; + static const char *const f[16] = { "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", + "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15" }; + static const char *const ar[64] = { + "ar0", "ar1", "ar2", "ar3", "ar4", "ar5", "ar6", "ar7", "ar8", "ar9", "ar10", "ar11", + "ar12", "ar13", "ar14", "ar15", "ar16", "ar17", "ar18", "ar19", "ar20", "ar21", "ar22", + "ar23", "ar24", "ar25", "ar26", "ar27", "ar28", "ar29", "ar30", "ar31", "ar32", "ar33", + "ar34", "ar35", "ar36", "ar37", "ar38", "ar39", "ar40", "ar41", "ar42", "ar43", "ar44", + "ar45", "ar46", "ar47", "ar48", "ar49", "ar50", "ar51", "ar52", "ar53", "ar54", "ar55", + "ar56", "ar57", "ar58", "ar59", "ar60", "ar61", "ar62", "ar63" }; + if (n == GR_PC) + return "pc"; + if (n >= GR_AR && n < GR_AR + 64) + return ar[n - GR_AR]; + if (n >= GR_LBEG && n < GR_F) + return sr[n - GR_LBEG]; + if (n >= GR_F && n < GR_F + 16) + return f[n - GR_F]; + if (n == GR_FCR) + return "fcr"; + if (n == GR_FSR) + return "fsr"; + if (n >= GR_A && n < GR_A + 16) + return a[n - GR_A]; + return 0; +} + +static const int g_regs[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, -1 }; + +/* the disassembly: the instruction's bytes and size (the debugger and + * the trace show the mnemonic from the ELF's own tools) */ +static int core_disasm(struct lx6 *c, u32 addr, char *text, size_t cap) +{ + u32 lo, hi = 0, w; + int len; + if (esk_bus_debug_read(&c->m->bus, addr, 2, &lo) != 0) + return 0; + len = (lo & 15u) >= 8u ? 2 : 3; + if (len == 3 && esk_bus_debug_read(&c->m->bus, addr + 2u, 1, &hi) != 0) + return 0; + w = (lo & 0xffffu) | (hi & 0xffu) << 16; + snprintf(text, cap, len == 3 ? ".byte 0x%02x, 0x%02x, 0x%02x" : ".byte 0x%02x, 0x%02x", + (unsigned)(w & 0xffu), (unsigned)((w >> 8) & 0xffu), (unsigned)((w >> 16) & 0xffu)); + return len; +} + +/* the interface's hooks: the core that runs next */ +static struct lx6 *cur(struct esk_cpu *cpu) +{ + struct smp *x = (struct smp *)cpu; + return x->c[current(x)]; +} + +static u32 smp_pc(struct esk_cpu *cpu) { return cur(cpu)->pc; } +static int smp_reg_read(struct esk_cpu *cpu, int n, u8 *buf) { return core_reg_read(cur(cpu), n, buf); } +static int smp_reg_write(struct esk_cpu *cpu, int n, const u8 *buf) { return core_reg_write(cur(cpu), n, buf); } +static const int *smp_g_regs(struct esk_cpu *cpu) { ES_UNUSED(cpu); return g_regs; } +static const char *smp_xml(struct esk_cpu *cpu, const char *annex) +{ + ES_UNUSED(cpu); + ES_UNUSED(annex); + return 0; +} +static void smp_interrupt(struct esk_cpu *cpu, int n) +{ + struct smp *x = (struct smp *)cpu; + struct lx6 *c = x->c[current(x)]; + if (n >= 16) + n -= 16; + if (n >= 0 && n < 32 && ((1u << n) & INT_SOFTWARE)) { + c->latched |= 1u << n; + settle(x, current(x)); + } +} +static const char *smp_reg_name(struct esk_cpu *cpu, int n) { ES_UNUSED(cpu); return core_reg_name(n); } +static int smp_wake_pending(struct esk_cpu *cpu) +{ + struct smp *x = (struct smp *)cpu; + int i; + for (i = 0; i < x->n; i++) + if (!x->held[i] && !x->stalled[i] && core_wake_pending(x->c[i])) + return 1; + return 0; +} +static int smp_disasm(struct esk_cpu *cpu, u32 addr, char *text, size_t cap) +{ + return core_disasm(cur(cpu), addr, text, cap); +} + +static const struct esk_cpu_ops smp_ops = { + "xtensa", 94, "an Xtensa image", + smp_reset, smp_step, smp_pc, smp_reg_read, smp_reg_write, + smp_g_regs, smp_xml, 3, smp_interrupt, GR_PC, 1, 0, smp_reg_name, smp_wake_pending, + smp_disasm, 0, 0, +}; + +/* each core's own interface, for the analyses' step (esk_mach_step_core) */ +static u32 one_pc(struct esk_cpu *cpu) { return ((struct lx6 *)cpu)->pc; } +static int one_reg_read(struct esk_cpu *cpu, int n, u8 *buf) { return core_reg_read((struct lx6 *)cpu, n, buf); } +static int one_reg_write(struct esk_cpu *cpu, int n, const u8 *buf) { return core_reg_write((struct lx6 *)cpu, n, buf); } +static void one_reset(struct esk_cpu *cpu) { core_reset((struct lx6 *)cpu); } +static int one_wake_pending(struct esk_cpu *cpu) { return core_wake_pending((struct lx6 *)cpu); } +static int one_disasm(struct esk_cpu *cpu, u32 addr, char *text, size_t cap) +{ + return core_disasm((struct lx6 *)cpu, addr, text, cap); +} + +static const struct esk_cpu_ops one_ops = { + "xtensa", 94, "an Xtensa image", + one_reset, one_step, one_pc, one_reg_read, one_reg_write, + smp_g_regs, smp_xml, 3, 0, GR_PC, 1, 0, smp_reg_name, one_wake_pending, + one_disasm, 0, 0, +}; + +int esk_lx6_is(const struct esk_cpu *c) +{ + return c && c->ops == &smp_ops; +} + +es_status esk_lx6_create(struct esk_mach *m, const char *model, int cores, struct esk_cpu **out) +{ + struct smp *x; + int i; + es_status st; + if (strcmp(model, "esp32-lx6") || cores < 1 || cores > NCORES) + return ES_ENOTFOUND; + x = (struct smp *)esk_mach_alloc(m, sizeof *x); + if (!x) + return ES_ENOMEM; + x->cpu.ops = &smp_ops; + x->m = m; + x->n = cores; + for (i = 0; i < cores; i++) { + struct lx6 *c = (struct lx6 *)esk_mach_alloc(m, sizeof *c); + if (!c) + return ES_ENOMEM; + c->cpu.ops = &one_ops; + c->m = m; + c->x = x; + c->id = i; + c->prid = i == 0 ? 0xCDCDu : 0xABABu; + c->reset_vector = 1; + x->c[i] = c; + } + st = esk_mach_add_dev(m, 0, 0, &timer_ops, x); + if (st != ES_OK) + return st; + esk_mach_clock(m, &x->running, 1); /* the CPU timers' clock never stops */ + m->steps_cores = 1; + m->cpu = &x->cpu; + smp_reset(&x->cpu); + *out = &x->cpu; + return ES_OK; +} + +/* ---- the part's controls ---------------------------------------------- */ + +int esk_lx6_cores(struct esk_mach *m) +{ + struct smp *x = smp_of(m); + return x ? x->n : 0; +} + +int esk_lx6_core(struct esk_mach *m) +{ + struct smp *x = smp_of(m); + return x ? current(x) : 0; +} + +void esk_lx6_irq(struct esk_mach *m, int core, int line, int level) +{ + struct smp *x = smp_of(m); + struct lx6 *c; + u32 bit; + if (!x || core < 0 || core >= x->n || line < 0 || line > 31) + return; + c = x->c[core]; + bit = 1u << line; + if (level) { + if (!(c->lines & bit) && (bit & (INT_EDGE | INT_NMI))) + c->latched |= bit; + c->lines |= bit; + } else { + c->lines &= ~bit; + } + if (x->state[core] == CORE_SLEEP && core_wake_pending(c)) + settle(x, core); +} + +void esk_lx6_core_reset(struct esk_mach *m, int core, int held) +{ + struct smp *x = smp_of(m); + if (!x || core < 0 || core >= x->n || x->held[core] == (held != 0)) + return; + x->held[core] = held != 0; + if (!held) { + core_reset(x->c[core]); + x->lt[core] = m->cycles; + } + settle(x, core); +} + +void esk_lx6_core_stall(struct esk_mach *m, int core, int why, int stalled) +{ + struct smp *x = smp_of(m); + if (!x || core < 0 || core >= x->n || why < 0 || why > 7) + return; + if (stalled) + x->stalled[core] |= 1 << why; + else + x->stalled[core] &= ~(1 << why); + settle(x, core); +} + +void esk_lx6_core_restart(struct esk_mach *m, int core) +{ + struct smp *x = smp_of(m); + if (!x || core < 0 || core >= x->n) + return; + core_reset(x->c[core]); + x->lt[core] = m->cycles; + if (x->stepping && x->cur == core) + x->c[core]->exc = 3; /* the instruction in progress is gone with it */ + settle(x, core); +} + +int esk_lx6_view(struct esk_mach *m, int core, u32 base, u32 size, const u8 *mem) +{ + struct smp *x = smp_of(m); + struct lx6 *c; + if (!x || core < 0 || core >= x->n) + return -1; + c = x->c[core]; + if (c->nview >= 2) + return -1; + c->view[c->nview].base = base; + c->view[c->nview].size = size; + c->view[c->nview].mem = mem; + c->nview++; + return 0; +} + +void esk_lx6_reset_vector(struct esk_mach *m, int core, int sel) +{ + struct smp *x = smp_of(m); + if (x && core >= 0 && core < x->n) + x->c[core]->reset_vector = sel != 0; +} + +u32 esk_lx6_pc(struct esk_mach *m, int core) +{ + struct smp *x = smp_of(m); + return x && core >= 0 && core < x->n ? x->c[core]->pc : 0; +} + +u32 esk_lx6_sp(struct esk_mach *m, int core) +{ + struct smp *x = smp_of(m); + return x && core >= 0 && core < x->n ? AR(x->c[core], 1) : 0; +} + +/* ---- for lx6fp.c ------------------------------------------------------ */ + +u32 *esk_lx6_fregs(struct lx6 *c) { return c->f; } +u32 *esk_lx6_fcr(struct lx6 *c) { return &c->fcr; } +u32 *esk_lx6_fsr(struct lx6 *c) { return &c->fsr; } +u32 esk_lx6_fp_rd(struct lx6 *c, int n) { return c->f[n & 15]; } +void esk_lx6_fp_wr(struct lx6 *c, int n, u32 v) { c->f[n & 15] = v; } +u32 esk_lx6_ar(struct lx6 *c, int n) { return AR(c, (u32)n); } +void esk_lx6_set_ar(struct lx6 *c, int n, u32 v) { AR(c, (u32)n) = v; } +u32 esk_lx6_br(struct lx6 *c) { return c->br; } +void esk_lx6_set_br(struct lx6 *c, u32 v) { c->br = v & 0xffffu; } +int esk_lx6_win(struct lx6 *c, u32 hi) { return win(c, hi); } diff --git a/core/mcu/xtensa/lx6.h b/core/mcu/xtensa/lx6.h new file mode 100644 index 000000000..623d1ec89 --- /dev/null +++ b/core/mcu/xtensa/lx6.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* lx6.h -- the Xtensa LX6 cores of the ESP32 (lx6.c, lx6fp.c), behind + * the machine's one core interface: the configuration Espressif's + * core-isa.h gives (esp32_v3_49_prod, LX6.0.3), two of them as the chip's + * PRO_CPU and APP_CPU. */ +#ifndef ESK_MCU_XTENSA_LX6_H +#define ESK_MCU_XTENSA_LX6_H + +#include "mcu/mach.h" + +/* the cores of model "esp32-lx6" (`cores` 1 or 2): core 0 runs from + * reset, core 1 is held in reset until the part releases it */ +es_status esk_lx6_create(struct esk_mach *m, const char *model, int cores, struct esk_cpu **out); +int esk_lx6_is(const struct esk_cpu *c); +/* the number of cores, and the one whose instruction is in progress (a + * debugger's: the one that runs next) */ +int esk_lx6_cores(struct esk_mach *m); +int esk_lx6_core(struct esk_mach *m); +/* interrupt input `line` (0-31) of core `core` is at `level`: a level + * input is pending while it is high, an edge input latches a rise + * (core-isa.h's XCHAL_INTTYPE_MASK_EXTERN_EDGE) */ +void esk_lx6_irq(struct esk_mach *m, int core, int line, int level); +/* the core is held in reset (1) or released (0): released, it starts + * at its reset vector */ +void esk_lx6_core_reset(struct esk_mach *m, int core, int held); +/* the core's clock is gated or stalled (1: it stops where it is) for + * reason `why` (0-7: the part's sources, any of which stops it), or runs */ +void esk_lx6_core_stall(struct esk_mach *m, int core, int why, int stalled); +/* the core alone is reset (a software or watchdog CPU reset): it starts + * again at its reset vector, the devices as they are */ +void esk_lx6_core_restart(struct esk_mach *m, int core); +/* the core sees `size` bytes of read-only memory at `base` from `mem` + * rather than the bus (its own ROM): 0, or -1 (two views at most) */ +int esk_lx6_view(struct esk_mach *m, int core, u32 base, u32 size, const u8 *mem); +/* the core's reset vector: 0x40000400 (1) or 0x50000000 (0) */ +void esk_lx6_reset_vector(struct esk_mach *m, int core, int sel); +/* the core's program counter, and its stack pointer (a1) */ +u32 esk_lx6_pc(struct esk_mach *m, int core); +u32 esk_lx6_sp(struct esk_mach *m, int core); + +/* ---- inside: the core's state, for lx6fp.c ------------------------------ */ + +struct lx6; +/* FP instruction `w` (op0 0 with op1 8, 10 or 11; op0 3): 0 when done, + * or -1 when not an FP instruction */ +int esk_lx6_fp(struct lx6 *c, u32 w); +u32 esk_lx6_fp_rd(struct lx6 *c, int n); +void esk_lx6_fp_wr(struct lx6 *c, int n, u32 v); +u32 *esk_lx6_fregs(struct lx6 *c); +u32 *esk_lx6_fcr(struct lx6 *c); +u32 *esk_lx6_fsr(struct lx6 *c); +u32 esk_lx6_ar(struct lx6 *c, int n); +void esk_lx6_set_ar(struct lx6 *c, int n, u32 v); +u32 esk_lx6_br(struct lx6 *c); +void esk_lx6_set_br(struct lx6 *c, u32 v); +/* a load or store of the core's: 0, or -1 when it took an exception */ +int esk_lx6_load(struct lx6 *c, u32 a, int n, u32 *v); +/* the window overflow check of registers up to `hi`: 1 when it was taken */ +int esk_lx6_win(struct lx6 *c, u32 hi); +int esk_lx6_store(struct lx6 *c, u32 a, int n, u32 v); +void esk_lx6_illegal(struct lx6 *c); +#endif diff --git a/core/mcu/xtensa/lx6fp.c b/core/mcu/xtensa/lx6fp.c new file mode 100644 index 000000000..411a11b62 --- /dev/null +++ b/core/mcu/xtensa/lx6fp.c @@ -0,0 +1,418 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* lx6fp.c -- the ESP32's floating-point coprocessor (CP0): the Xtensa + * Floating-Point Coprocessor Option, single precision, with the + * division, reciprocal and square root instructions of core-isa.h + * (XCHAL_HAVE_FP_DIV, _RECIP, _SQRT, _RSQRT). + * + * Sixteen 32-bit registers f0-f15; FCR's rounding mode (bits 1:0: + * nearest even, toward zero, up, down) and FSR's sticky flags (bits 11:7: + * inexact, underflow, overflow, divide by zero, invalid). Arithmetic is + * IEEE 754: ADD.S, SUB.S, MUL.S, MADD.S and MSUB.S (fused, one rounding) + * are rounded as FCR says, with their flags; FLOAT.S and UFLOAT.S scale + * by 2^-imm, ROUND.S, TRUNC.S, FLOOR.S, CEIL.S and UTRUNC.S by 2^imm, out + * of range or NaN giving the limits (0x7fffffff, 0x80000000, or for + * UTRUNC.S 0xffffffff); the comparisons set a boolean register; the + * moves (MOV.S, ABS.S, NEG.S, the conditional ones, RFR, WFR, CONST.S's + * 0, 1, 2 and 0.5) change no flag. A NaN result is the first NaN operand + * made quiet, or 0x7fc00000. + * + * Where it differs inside: the division and square root sequences + * libgcc runs (DIV0.S, NEXP01.S, MADDN.S, MKDADJ.S, ADDEXP.S, ADDEXPM.S, + * DIVN.S; SQRT0.S, MKSADJ.S, ...) give the quotient or root correctly + * rounded, as the hardware's do, but the registers in between hold other + * values: MKDADJ.S fr, fs computes fs / fr, MKSADJ.S the root, ADDEXPM.S + * moves it to its destination, the others change nothing (Espressif's + * QEMU does the same). RECIP0.S and RSQRT0.S give the exact reciprocal + * and reciprocal root rounded, where the hardware gives a seed of fewer + * bits. The arithmetic is the host's double precision with fma(), which + * holds every single-precision result exactly or with its residual. */ +#include +#include +#include + +#include "mcu/xtensa/lx6.h" + +#define F_I 0x1u /* the flags, as FSR holds them from bit 7 */ +#define F_U 0x2u +#define F_O 0x4u +#define F_Z 0x8u +#define F_V 0x10u +#define QNAN 0x7fc00000u + +static float f_of(u32 v) +{ + float f; + memcpy(&f, &v, 4); + return f; +} + +static u32 bits_of(float f) +{ + u32 v; + memcpy(&v, &f, 4); + return v; +} + +static int is_nan(u32 v) +{ + return (v & 0x7f800000u) == 0x7f800000u && (v & 0x7fffffu); +} + +static int is_snan(u32 v) +{ + return is_nan(v) && !(v & 0x400000u); +} + +static void flag(struct lx6 *c, u32 f) +{ + *esk_lx6_fsr(c) |= f << 7; +} + +static u32 rmode(struct lx6 *c) +{ + return *esk_lx6_fcr(c) & 3u; +} + +/* a NaN result of operands a and b (and c): the first NaN quieted */ +static u32 nan_of(struct lx6 *c, u32 a, u32 b, u32 x, int n) +{ + if (is_snan(a) || is_snan(b) || (n > 2 && is_snan(x))) + flag(c, F_V); + if (is_nan(a)) + return a | 0x400000u; + if (is_nan(b)) + return b | 0x400000u; + if (n > 2 && is_nan(x)) + return x | 0x400000u; + return QNAN; +} + +/* the value hi + lo (lo below hi's last bit, only its sign and whether + * it is 0 matter) rounded to single precision in mode `m` */ +static u32 round_f(struct lx6 *c, double hi, double lo, u32 m) +{ + float f; + double diff, res; + int inexact; + if (hi != hi) + return QNAN; + if (isinf(hi)) + return bits_of((float)hi); + f = (float)hi; + if (isinf(f)) { /* overflow */ + flag(c, F_O | F_I); + if ((m == 1) || (m == 2 && hi < 0) || (m == 3 && hi > 0)) + return bits_of(hi < 0 ? -FLT_MAX : FLT_MAX); + return bits_of(f); + } + diff = hi - (double)f; + res = diff != 0 ? diff : lo; + inexact = diff != 0 || lo != 0; + if (m == 0) { + /* a tie broken the wrong way by the host, lo deciding */ + if (diff != 0 && lo != 0 && (lo > 0) == (diff > 0)) { + float g = nextafterf(f, diff > 0 ? INFINITY : -INFINITY); + if (fabs((double)g - (double)f) == 2 * fabs(diff)) + f = g; + } + } else if (res != 0) { + if (m == 1) { /* toward zero */ + if ((f > 0 && res < 0) || (f < 0 && res > 0)) + f = nextafterf(f, 0.0f); + } else if (m == 2) { /* up */ + if (res > 0) + f = nextafterf(f, INFINITY); + } else if (res < 0) { /* down */ + f = nextafterf(f, -INFINITY); + } + if (f == 0 && hi != 0) + f = hi < 0 ? -0.0f : 0.0f; + } + if (isinf(f)) { + flag(c, F_O | F_I); + return bits_of(f); + } + if (inexact) { + flag(c, F_I); + if (fabs((double)f) < FLT_MIN || (f == 0 && hi != 0)) + flag(c, F_U); + } + return bits_of(f); +} + +static int is_inf(u32 v) +{ + return (v & 0x7fffffffu) == 0x7f800000u; +} + +static int is_zero(u32 v) +{ + return !(v & 0x7fffffffu); +} + +/* a + b * x fused (sub: a - b * x), or b + x (mul 0) / b * x (add 0) */ +static u32 add_s(struct lx6 *c, u32 a, u32 b) +{ + double x = f_of(a), y = f_of(b), s, bb, lo; + if (is_nan(a) || is_nan(b)) + return nan_of(c, a, b, 0, 2); + if (is_inf(a) && is_inf(b) && ((a ^ b) & 0x80000000u)) { + flag(c, F_V); + return QNAN; + } + s = x + y; + if (is_inf(a) || is_inf(b)) + return bits_of((float)s); + if (s == 0 && x == -y && x != 0) + return rmode(c) == 3 ? 0x80000000u : 0; /* an exact zero sum: +0 but rounding down */ + if (s == 0) + return (a & b & 0x80000000u) | (rmode(c) == 3 ? (a | b) & 0x80000000u : 0u); + bb = s - x; + lo = (x - (s - bb)) + (y - bb); /* TwoSum: s + lo is x + y exactly */ + return round_f(c, s, lo, rmode(c)); +} + +static u32 mul_s(struct lx6 *c, u32 a, u32 b) +{ + if (is_nan(a) || is_nan(b)) + return nan_of(c, a, b, 0, 2); + if ((is_inf(a) && is_zero(b)) || (is_zero(a) && is_inf(b))) { + flag(c, F_V); + return QNAN; + } + return round_f(c, (double)f_of(a) * (double)f_of(b), 0, rmode(c)); /* exact in double */ +} + +static u32 madd_s(struct lx6 *c, u32 acc, u32 a, u32 b, int sub) +{ + double p, x, s, bb, lo; + u32 pb; + if (is_nan(acc) || is_nan(a) || is_nan(b)) { + if ((is_inf(a) && is_zero(b)) || (is_zero(a) && is_inf(b))) + flag(c, F_V); + return nan_of(c, a, b, acc, 3); + } + if ((is_inf(a) && is_zero(b)) || (is_zero(a) && is_inf(b))) { + flag(c, F_V); + return QNAN; + } + p = (double)f_of(a) * (double)f_of(b); + if (sub) + p = -p; + x = f_of(acc); + pb = (u32)(p < 0 || (p == 0 && signbit(p))) << 31; + if ((is_inf(a) || is_inf(b)) && is_inf(acc) && ((pb ^ acc) & 0x80000000u)) { + flag(c, F_V); + return QNAN; + } + s = x + p; + if (isinf(s) || is_inf(acc)) + return bits_of((float)s); + if (s == 0) { + if (x == 0 && p == 0) + return (acc & pb) | (rmode(c) == 3 ? (acc | pb) & 0x80000000u : 0u); + return rmode(c) == 3 ? 0x80000000u : 0; + } + bb = s - x; + lo = (x - (s - bb)) + (p - bb); + return round_f(c, s, lo, rmode(c)); +} + +static u32 div_s(struct lx6 *c, u32 a, u32 b) +{ + double q, r; + if (is_nan(a) || is_nan(b)) + return nan_of(c, a, b, 0, 2); + if ((is_zero(a) && is_zero(b)) || (is_inf(a) && is_inf(b))) { + flag(c, F_V); + return QNAN; + } + if (is_zero(b)) { + if (!is_inf(a)) + flag(c, F_Z); + return ((a ^ b) & 0x80000000u) | 0x7f800000u; + } + if (is_inf(a) || is_zero(a) || is_inf(b)) + return bits_of(f_of(a) / f_of(b)); + q = (double)f_of(a) / (double)f_of(b); + r = fma(-q, (double)f_of(b), (double)f_of(a)); /* the exact remainder */ + return round_f(c, q, (f_of(b) < 0 ? -r : r), rmode(c)); +} + +static u32 sqrt_s(struct lx6 *c, u32 a) +{ + double x = f_of(a), r, e; + if (is_nan(a)) + return nan_of(c, a, 0, 0, 1); + if (is_zero(a)) + return a; + if (a & 0x80000000u) { + flag(c, F_V); + return QNAN; + } + if (is_inf(a)) + return a; + r = sqrt(x); + e = fma(-r, r, x); + return round_f(c, r, e, rmode(c)); +} + +/* to an integer: v * 2^scale rounded in mode m, signed or not */ +static u32 to_int(struct lx6 *c, u32 v, u32 scale, u32 m, int uns) +{ + double x = ldexp((double)f_of(v), (int)scale), y; + if (is_nan(v)) { + flag(c, F_V); + return uns ? 0xffffffffu : 0x7fffffffu; + } + y = m == 0 ? nearbyint(x) : m == 1 ? trunc(x) : m == 2 ? ceil(x) : floor(x); + if (m == 0 && fabs(x - trunc(x)) == 0.5) + y = 2.0 * nearbyint(x / 2.0); /* ties to even, whatever the host's mode */ + if (uns && !(v & 0x80000000u)) { + if (y > 4294967295.0) { + flag(c, F_V); + return 0xffffffffu; + } + if (y != x) + flag(c, F_I); + return (u32)y; + } + if (y > 2147483647.0) { + flag(c, F_V); + return 0x7fffffffu; + } + if (y < -2147483648.0) { + flag(c, F_V); + return 0x80000000u; + } + if (uns && y != 0) + flag(c, F_V); /* UTRUNC.S of a negative value */ + else if (y != x) + flag(c, F_I); + return (u32)(s32)y; +} + +static u32 from_int(struct lx6 *c, u32 v, u32 scale, int uns) +{ + double x = uns ? (double)v : (double)(s32)v; + return round_f(c, ldexp(x, -(int)scale), 0, rmode(c)); /* exact in double */ +} + +/* the comparisons: 1 UN, 2 OEQ, 3 UEQ, 4 OLT, 5 ULT, 6 OLE, 7 ULE */ +static int compare(struct lx6 *c, u32 op, u32 a, u32 b) +{ + int un = is_nan(a) || is_nan(b); + float x = f_of(a), y = f_of(b); + if (is_snan(a) || is_snan(b) || (un && op >= 4 && (op & 1u) == 0)) + flag(c, F_V); + switch (op) { + case 1: return un; + case 2: return !un && x == y; + case 3: return un || x == y; + case 4: return !un && x < y; + case 5: return un || x < y; + case 6: return !un && x <= y; + case 7: return un || x <= y; + } + return 0; +} + +int esk_lx6_fp(struct lx6 *c, u32 w) +{ + u32 op1 = (w >> 16) & 15u, op2 = (w >> 20) & 15u, r = (w >> 12) & 15u, s = (w >> 8) & 15u, + t = (w >> 4) & 15u, *f = esk_lx6_fregs(c), a, v; + if (op1 == 8) { /* LSX, SSX (LSXU, SSXU: not in this configuration) */ + if (op2 != 0 && op2 != 4) + return -1; + if (esk_lx6_win(c, s > t ? s : t)) + return 0; + a = esk_lx6_ar(c, (int)s) + esk_lx6_ar(c, (int)t); + if (op2 == 0) { + if (esk_lx6_load(c, a, 4, &v) == 0) + f[r] = v; + } else { + (void)esk_lx6_store(c, a, 4, f[r]); + } + return 0; + } + if (op1 == 10) { /* FP0 */ + switch (op2) { + case 0: f[r] = add_s(c, f[s], f[t]); return 0; + case 1: f[r] = add_s(c, f[s], f[t] ^ (is_nan(f[t]) ? 0u : 0x80000000u)); return 0; + case 2: f[r] = mul_s(c, f[s], f[t]); return 0; + case 4: f[r] = madd_s(c, f[r], f[s], f[t], 0); return 0; + case 5: f[r] = madd_s(c, f[r], f[s], f[t], 1); return 0; + case 6: case 7: return 0; /* MADDN.S DIVN.S: in MKDADJ.S's result */ + case 8: case 9: case 10: case 11: case 14: { /* ROUND TRUNC FLOOR CEIL UTRUNC */ + static const u32 mode[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 2, 0, 0, 1, 0 }; + if (esk_lx6_win(c, r)) + return 0; + esk_lx6_set_ar(c, (int)r, to_int(c, f[s], t, mode[op2], op2 == 14)); + return 0; + } + case 12: case 13: /* FLOAT.S UFLOAT.S */ + if (esk_lx6_win(c, s)) + return 0; + f[r] = from_int(c, esk_lx6_ar(c, (int)s), t, op2 == 13); + return 0; + case 15: /* FP1OP */ + switch (t) { + case 0: f[r] = f[s]; return 0; /* MOV.S */ + case 1: f[r] = f[s] & 0x7fffffffu; return 0; /* ABS.S */ + case 3: { /* CONST.S */ + static const u32 k[4] = { 0x00000000u, 0x3f800000u, 0x40000000u, 0x3f000000u }; + f[r] = k[s & 3u]; + return 0; + } + case 4: /* RFR */ + if (esk_lx6_win(c, r)) + return 0; + esk_lx6_set_ar(c, (int)r, f[s]); + return 0; + case 5: /* WFR */ + if (esk_lx6_win(c, s)) + return 0; + f[r] = esk_lx6_ar(c, (int)s); + return 0; + case 6: f[r] = f[s] ^ 0x80000000u; return 0; /* NEG.S */ + case 7: case 9: case 11: return 0; /* DIV0.S SQRT0.S NEXP01.S */ + case 8: f[r] = div_s(c, 0x3f800000u, f[s]); return 0; /* RECIP0.S */ + case 10: /* RSQRT0.S */ + f[r] = div_s(c, 0x3f800000u, sqrt_s(c, f[s])); + return 0; + case 12: f[r] = sqrt_s(c, f[s]); return 0; /* MKSADJ.S */ + case 13: f[r] = div_s(c, f[s], f[r]); return 0; /* MKDADJ.S */ + case 14: return 0; /* ADDEXP.S */ + case 15: f[r] = f[s]; return 0; /* ADDEXPM.S */ + } + return -1; + } + return -1; + } + if (op1 == 11) { /* FP1 */ + if (op2 >= 1 && op2 <= 7) { + u32 br = esk_lx6_br(c); + int b = compare(c, op2, f[s], f[t]); + esk_lx6_set_br(c, (br & ~(1u << r)) | (u32)b << r); + return 0; + } + if (op2 >= 8 && op2 <= 11) { /* MOVEQZ.S MOVNEZ.S MOVLTZ.S MOVGEZ.S */ + int mv; + if (esk_lx6_win(c, t)) + return 0; + v = esk_lx6_ar(c, (int)t); + mv = op2 == 8 ? v == 0 : op2 == 9 ? v != 0 : op2 == 10 ? (s32)v < 0 : (s32)v >= 0; + if (mv) + f[r] = f[s]; + return 0; + } + if (op2 == 12 || op2 == 13) { /* MOVF.S MOVT.S */ + if (((esk_lx6_br(c) >> t) & 1u) == (op2 == 13)) + f[r] = f[s]; + return 0; + } + return -1; + } + return -1; +} diff --git a/tools/importers/esprom.py b/tools/importers/esprom.py index 74716ce75..dd95dacf8 100644 --- a/tools/importers/esprom.py +++ b/tools/importers/esprom.py @@ -98,7 +98,9 @@ def build(elf: bytes, base: int, size: int, tables=(), words=(), aliases=()) -> entries = [struct.unpack_from("<3I", img, o - base) for o in range(start, end, 16)] for addr, data in sections(elf): for dst, dst_end, src in entries: - if dst <= addr and addr + len(data) <= dst_end and dst < dst_end: + if dst <= addr < dst_end: + # a section may outrun its entry by its alignment padding + # (the ESP32's .data_xtos_pro, 0x420 bytes for 0x418) o = src + (addr - dst) - base if 0 <= o and o + len(data) <= size: img[o:o + len(data)] = data From aa738f8c09bd867ebdc882c01f0fca60ca04475f Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Sun, 8 Nov 2026 03:35:14 +0000 Subject: [PATCH 43/57] ESP32: DevKitC V4 board, LX6 unit test, ESP-IDF vendor scenario, docs - emblink/esp32_devkitc: the DevKitC V4's power path (BAT760, AMS1117-3.3), power LED, Boot and EN buttons with EN's RC, both 19-pin headers, from Espressif's schematic; three fixtures - test_lx6: programs assembled by Espressif's assembler (gen_lx6_programs.py -> lx6_programs.h) check the core's instructions, windowed recursion through the ROM's window vectors, exceptions, the timer interrupt, FPU, MAC16, and the APP_CPU's ROM data - tests/vendor/esp-idf: esp32_hello_world built for the esp32 target - the description's fidelity and provenance, CHANGELOG, STATUS, coverage - a zero-overhead loop is not taken back when its last instruction branches to LEND (the ROM's strcmp); the DFP accelerator's user registers; no debug hooks in the core --- CHANGELOG.md | 22 + catalog/boards/esp32_devkitc/board.json | 553 +++++++++++++++++- .../esp32_devkitc/tests/boot-button.json | 69 +++ .../esp32_devkitc/tests/reset-button.json | 78 +++ .../esp32_devkitc/tests/usb-powered.json | 82 +++ catalog/mcus/espressif/esp32/mcu.json | 56 +- core/mcu/mcu.c | 4 +- core/mcu/xtensa/lx6.c | 89 +-- docs/STATUS.md | 15 + docs/coverage.md | 56 ++ tests/unit/gen_lx6_programs.py | 294 ++++++++++ tests/unit/lx6_programs.h | 312 ++++++++++ tests/unit/test_lx6.c | 351 +++++++++++ .../examples/esp32_hello_world/project.json | 61 ++ .../examples/esp32_hello_world/scenario.json | 82 +++ tests/vendor/esp-idf/run.py | 23 +- 16 files changed, 2027 insertions(+), 120 deletions(-) create mode 100644 catalog/boards/esp32_devkitc/tests/boot-button.json create mode 100644 catalog/boards/esp32_devkitc/tests/reset-button.json create mode 100644 catalog/boards/esp32_devkitc/tests/usb-powered.json create mode 100644 tests/unit/gen_lx6_programs.py create mode 100644 tests/unit/lx6_programs.h create mode 100644 tests/unit/test_lx6.c create mode 100644 tests/vendor/esp-idf/examples/esp32_hello_world/project.json create mode 100644 tests/vendor/esp-idf/examples/esp32_hello_world/scenario.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 1094af9ac..f7b911d9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,28 @@ versions follow semantic versioning. There is no release yet. ## Unreleased +### ESP32 in the machine framework: ESP-IDF boots on the ESP32-DevKitC V4 + +- Added the ESP32's two Xtensa LX6 cores as a framework core + (`core/mcu/xtensa/lx6.c`, `lx6fp.c`, backend `xtensa`): the configuration + Espressif's core-isa.h gives, windowed registers with their overflow and + underflow, loops, XEA2 exceptions, 32 interrupts at their levels, the + CCOMPARE timers, MAC16, booleans, the FPU, region protection and the debug + option; the APP_CPU held until DPORT releases it, with its own view of the + ROM's data pointers. +- Added `espressif/esp32`: the memory map, the ROM image made from + Espressif's ROM ELF (`esprom.py` now places sections that outrun their + copy-table entry; the image equals QEMU's esp32-v3-rom.bin), the register + file from esp32.svd, and models for DPORT (APP_CPU, caches and flash MMU, + interrupt matrix), RTC_CNTL, eFuse, APB_CTRL clocks, GPIO/IO_MUX with + strapping, UART0-2, TIMG0/1, SPI0/1 with the GT25Q32A flash, SHA, the + internal analog I2C, RNG and the AHB view. +- Added the `emblink/esp32_devkitc` board (ESP32-DevKitC V4, WROOM-32E) with + its power path, LED, Boot and EN buttons, headers and fixtures. +- Espressif's ROM, ESP-IDF v5.4's bootloader and hello_world run unmodified + through `esp_restart` (`tests/vendor/esp-idf`, example `esp32_hello_world`, + 9/9); `test_lx6` checks the core with programs from Espressif's assembler. + ### Original ESP32 digital per-pin hold - Added the SDK-mapped digital hold register and per-pad capture masks. diff --git a/catalog/boards/esp32_devkitc/board.json b/catalog/boards/esp32_devkitc/board.json index 2068109da..d98c1ccf4 100644 --- a/catalog/boards/esp32_devkitc/board.json +++ b/catalog/boards/esp32_devkitc/board.json @@ -2,24 +2,541 @@ "schema_version": 1, "kind": "board", "id": "emblink/esp32_devkitc", - "version": "0.1.0", + "version": "1.0.0", "name": "ESP32-DevKitC V4", - "description": "Espressif's ESP32-DevKitC V4 with an ESP32-WROOM-32E module.", - "keywords": ["esp32", "devkitc", "esp32-wroom-32e", "xtensa", "espressif"], + "description": "Espressif's ESP32-DevKitC V4 with an ESP32-WROOM-32E module (the ESP32-D0WD-V3 with its 40 MHz crystal and 4 MB of flash), the AMS1117-3.3 regulator making 3.3 V from the micro-USB's 5 V through a BAT760 Schottky, the red power LED, the Boot button on GPIO0, the EN button on CHIP_PU with its RC, and two 19-pin headers. The CP2102N's USB-to-UART bridge on UART0 (GPIO1/GPIO3) is the session's serial console, and a serial port on the host (embsim serve --serial). The firmware is a flash image (bin), as idf.py and esptool write it.", + "keywords": [ + "esp32", + "devkitc", + "esp32-devkitc-v4", + "esp32-wroom-32e", + "xtensa", + "espressif", + "cp2102n" + ], "revision": "V4", - "mcu": {"ref": "espressif/esp32", "package": "wroom32e", "instance": "U1"}, - "oscillators": {"hse": {"hz": 40000000, "source": "external_crystal"}}, - "components": [], + "mcu": { + "ref": "espressif/esp32", + "package": "wroom32e", + "instance": "U1" + }, + "oscillators": { + "hse": { + "hz": 40000000, + "source": "external_crystal" + } + }, + "components": [ + { + "id": "D2", + "component": "emblink/diode_schottky", + "params": { + "part": "generic" + }, + "doc": "BAT760-7: the USB's VBUS to the board's 5 V (EXT_5V), so 5 V fed at the header does not reach the host (the generic Schottky's model)" + }, + { + "id": "U2", + "component": "emblink/regulator", + "params": { + "part": "AMS1117-3.3" + }, + "doc": "AMS1117-3.3: 3.3 V from 5 V for the module and the bridge" + }, + { + "id": "C21", + "component": "emblink/capacitor", + "params": { + "capacitance": "22u" + }, + "doc": "U2's input capacitor, 22 uF/10 V" + }, + { + "id": "C22", + "component": "emblink/capacitor", + "params": { + "capacitance": "100n" + }, + "doc": "U2's input decoupling, 0.1 uF/50 V" + }, + { + "id": "C1", + "component": "emblink/capacitor", + "params": { + "capacitance": "22u" + }, + "doc": "U2's output capacitor, 22 uF/10 V" + }, + { + "id": "C3", + "component": "emblink/capacitor", + "params": { + "capacitance": "22u" + }, + "doc": "The 3.3 V rail's bulk capacitor at the module, 22 uF/10 V" + }, + { + "id": "R2", + "component": "emblink/resistor", + "params": { + "resistance": "2k" + }, + "doc": "D1's resistor" + }, + { + "id": "D1", + "component": "emblink/led", + "params": { + "color": "red" + }, + "doc": "The power-on LED" + }, + { + "id": "R11", + "component": "emblink/resistor", + "params": { + "resistance": "10k" + }, + "doc": "EN's pull-up to 3.3 V" + }, + { + "id": "C9", + "component": "emblink/capacitor", + "params": { + "capacitance": "100n" + }, + "doc": "EN's 0.1 uF: with R11 it delays the chip's start after power-up (the RC the datasheet asks for)" + }, + { + "id": "SW2", + "component": "emblink/push_button", + "doc": "EN: joins EN to ground (reset)" + }, + { + "id": "SW1", + "component": "emblink/push_button", + "doc": "Boot: joins GPIO0 to ground; held through a reset, the ROM waits for a download" + }, + { + "id": "J2", + "component": "emblink/header", + "params": { + "pins": 19 + }, + "doc": "The left header (CON19X1_2P54)" + }, + { + "id": "J3", + "component": "emblink/header", + "params": { + "pins": 19 + }, + "doc": "The right header (CON19X1_2P54)" + } + ], "nets": { - "3V3": ["U1.VDD3P3"], - "GND": ["U1.GND"], - "EN": ["U1.CHIP_EN"], - "GPIO0": ["U1.GPIO0"], - "GPIO1": ["U1.GPIO1"], - "GPIO3": ["U1.GPIO3"] - }, - "ports": {}, - "console": {"peripheral": "UART0", "tx": "GPIO1", "rx": "GPIO3", "baud": 115200}, - "fidelity": {"functional": {"claims": "Work in progress.", "omissions": [], "validation": []}}, - "provenance": {"author": "EmbSIM", "license": "Apache-2.0", "sources": []} -} + "VBUS": [ + "D2.A" + ], + "5V": [ + "D2.K", + "U2.IN", + "C21.1", + "C22.1", + "J2.19" + ], + "3V3": [ + "U2.OUT", + "C1.1", + "C3.1", + "U1.VDD3P3", + "R2.1", + "R11.1", + "J2.1" + ], + "GND": [ + "U2.GND", + "C21.2", + "C22.2", + "C1.2", + "C3.2", + "D1.K", + "C9.2", + "SW2.2", + "SW1.2", + "U1.GND", + "J2.14", + "J3.1", + "J3.7" + ], + "D1_A": [ + "R2.2", + "D1.A" + ], + "EN": [ + "U1.CHIP_EN", + "R11.2", + "C9.1", + "SW2.1", + "J2.2" + ], + "GPIO36": [ + "U1.GPIO36", + "J2.3" + ], + "GPIO39": [ + "U1.GPIO39", + "J2.4" + ], + "GPIO34": [ + "U1.GPIO34", + "J2.5" + ], + "GPIO35": [ + "U1.GPIO35", + "J2.6" + ], + "GPIO32": [ + "U1.GPIO32", + "J2.7" + ], + "GPIO33": [ + "U1.GPIO33", + "J2.8" + ], + "GPIO25": [ + "U1.GPIO25", + "J2.9" + ], + "GPIO26": [ + "U1.GPIO26", + "J2.10" + ], + "GPIO27": [ + "U1.GPIO27", + "J2.11" + ], + "GPIO14": [ + "U1.GPIO14", + "J2.12" + ], + "GPIO12": [ + "U1.GPIO12", + "J2.13" + ], + "GPIO13": [ + "U1.GPIO13", + "J2.15" + ], + "GPIO23": [ + "U1.GPIO23", + "J3.2" + ], + "GPIO22": [ + "U1.GPIO22", + "J3.3" + ], + "GPIO1": [ + "U1.GPIO1", + "J3.4" + ], + "GPIO3": [ + "U1.GPIO3", + "J3.5" + ], + "GPIO21": [ + "U1.GPIO21", + "J3.6" + ], + "GPIO19": [ + "U1.GPIO19", + "J3.8" + ], + "GPIO18": [ + "U1.GPIO18", + "J3.9" + ], + "GPIO5": [ + "U1.GPIO5", + "J3.10" + ], + "GPIO17": [ + "U1.GPIO17", + "J3.11" + ], + "GPIO16": [ + "U1.GPIO16", + "J3.12" + ], + "GPIO4": [ + "U1.GPIO4", + "J3.13" + ], + "GPIO0": [ + "U1.GPIO0", + "J3.14", + "SW1.1" + ], + "GPIO2": [ + "U1.GPIO2", + "J3.15" + ], + "GPIO15": [ + "U1.GPIO15", + "J3.16" + ] + }, + "ports": { + "VBUS": { + "net": "VBUS", + "doc": "The micro-USB connector's VBUS: where a USB host's 5 V comes in" + }, + "3V3": { + "net": "3V3", + "connector": "J2", + "pin": 1 + }, + "EN": { + "net": "EN", + "connector": "J2", + "pin": 2, + "doc": "CHIP_PU: low holds the chip off and in reset" + }, + "VP": { + "net": "GPIO36", + "connector": "J2", + "pin": 3 + }, + "VN": { + "net": "GPIO39", + "connector": "J2", + "pin": 4 + }, + "IO34": { + "net": "GPIO34", + "connector": "J2", + "pin": 5 + }, + "IO35": { + "net": "GPIO35", + "connector": "J2", + "pin": 6 + }, + "IO32": { + "net": "GPIO32", + "connector": "J2", + "pin": 7 + }, + "IO33": { + "net": "GPIO33", + "connector": "J2", + "pin": 8 + }, + "IO25": { + "net": "GPIO25", + "connector": "J2", + "pin": 9 + }, + "IO26": { + "net": "GPIO26", + "connector": "J2", + "pin": 10 + }, + "IO27": { + "net": "GPIO27", + "connector": "J2", + "pin": 11 + }, + "IO14": { + "net": "GPIO14", + "connector": "J2", + "pin": 12 + }, + "IO12": { + "net": "GPIO12", + "connector": "J2", + "pin": 13 + }, + "GND": { + "net": "GND", + "connector": "J2", + "pin": 14 + }, + "IO13": { + "net": "GPIO13", + "connector": "J2", + "pin": 15 + }, + "5V": { + "net": "5V", + "connector": "J2", + "pin": 19 + }, + "GND1": { + "net": "GND", + "connector": "J3", + "pin": 1 + }, + "IO23": { + "net": "GPIO23", + "connector": "J3", + "pin": 2 + }, + "IO22": { + "net": "GPIO22", + "connector": "J3", + "pin": 3 + }, + "TX": { + "net": "GPIO1", + "connector": "J3", + "pin": 4, + "doc": "GPIO1, U0TXD: also the bridge's RXD" + }, + "RX": { + "net": "GPIO3", + "connector": "J3", + "pin": 5, + "doc": "GPIO3, U0RXD: also the bridge's TXD" + }, + "IO21": { + "net": "GPIO21", + "connector": "J3", + "pin": 6 + }, + "GND2": { + "net": "GND", + "connector": "J3", + "pin": 7 + }, + "IO19": { + "net": "GPIO19", + "connector": "J3", + "pin": 8 + }, + "IO18": { + "net": "GPIO18", + "connector": "J3", + "pin": 9 + }, + "IO5": { + "net": "GPIO5", + "connector": "J3", + "pin": 10 + }, + "IO17": { + "net": "GPIO17", + "connector": "J3", + "pin": 11 + }, + "IO16": { + "net": "GPIO16", + "connector": "J3", + "pin": 12 + }, + "IO4": { + "net": "GPIO4", + "connector": "J3", + "pin": 13 + }, + "IO0": { + "net": "GPIO0", + "connector": "J3", + "pin": 14, + "doc": "GPIO0, a strapping pin: the Boot button" + }, + "IO2": { + "net": "GPIO2", + "connector": "J3", + "pin": 15 + }, + "IO15": { + "net": "GPIO15", + "connector": "J3", + "pin": 16 + } + }, + "leds": [ + { + "name": "power", + "component": "D1", + "color": "red" + } + ], + "buttons": [ + { + "name": "boot", + "component": "SW1", + "gpio": "GPIO0", + "active": "low" + }, + { + "name": "reset", + "component": "SW2", + "signal": "CHIP_EN", + "active": "low" + } + ], + "console": { + "peripheral": "UART0", + "tx": "GPIO1", + "rx": "GPIO3", + "baud": 115200, + "bridge": { + "part": "CP2102N-A02-GQFN28", + "usb": { + "vid": "0x10C4", + "pid": "0xEA60", + "product": "CP2102N USB to UART Bridge Controller" + } + } + }, + "toolchains": { + "gcc-xtensa": { + "cpu": "esp32", + "flags": [ + "-mlongcalls" + ] + } + }, + "fidelity": { + "functional": { + "claims": "Nets, ports, the power LED and the two buttons as parts; the ESP32 runs its firmware from the flash image (see its description): Espressif's own ROM, the second-stage bootloader and the app, as on the board. EN pulls CHIP_PU low and the chip starts again with rst:0x1 (POWERON_RESET); Boot held through it makes the ROM wait for a download. The CP2102N's port is the session's console on UART0 (GPIO1/GPIO3) and a serial port on the host.", + "omissions": [ + "the CP2102N itself: its USB enumeration, its modem lines and so the auto-program circuit (Q1, Q2 and R21, R22 that let esptool's DTR and RTS drive EN and GPIO0): pressing Boot and EN does what they do", + "the ESD diodes on the USB lines (LESD5D5.0CT1G) and the USB connector", + "the flash pins GPIO6-11 on the headers (D0-D3, CMD, CLK): the module keeps them for its flash and the ports leave them out", + "the artwork and the bench model" + ], + "validation": [] + }, + "electrical": { + "claims": "The power path from the schematic: VBUS through the Schottky to 5 V, the AMS1117-3.3 to 3.3 V, the LED, the EN RC.", + "omissions": [], + "validation": [] + } + }, + "provenance": { + "author": "EmbSIM", + "license": "Apache-2.0", + "sources": [ + { + "title": "ESP32-DevKitC V4 schematic (Espressif)", + "rev": "V4, 2020-02-25", + "url": "https://dl.espressif.com/dl/schematics/esp32_devkitc_v4_sch.pdf" + }, + { + "title": "ESP32-DevKitC V4 user guide (Espressif esp-dev-kits)", + "url": "https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32/esp32-devkitc/user_guide.html" + }, + { + "title": "ESP32-WROOM-32E & ESP32-WROOM-32UE datasheet (Espressif)", + "rev": "v1.7" + } + ], + "notes": "Nets and parts read from the schematic's text and the user guide's header table." + }, + "tests": [ + "tests/*.json" + ] +} \ No newline at end of file diff --git a/catalog/boards/esp32_devkitc/tests/boot-button.json b/catalog/boards/esp32_devkitc/tests/boot-button.json new file mode 100644 index 000000000..d65737702 --- /dev/null +++ b/catalog/boards/esp32_devkitc/tests/boot-button.json @@ -0,0 +1,69 @@ +{ + "schema_version": 1, + "kind": "component_test", + "name": "boot-button", + "description": "Boot (SW1) joins GPIO0 to ground while held.", + "policy": "electrical", + "until": "60ms", + "circuit": { + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc" + } + ], + "components": [ + { + "id": "HOST", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5.0 + } + }, + { + "id": "CABLE", + "component": "emblink/resistor", + "params": { + "resistance": 0.25 + } + }, + { + "id": "G", + "component": "emblink/gnd" + } + ], + "nets": { + "HOSTV": [ + "HOST.+", + "CABLE.1" + ], + "VBUS": [ + "CABLE.2", + "B1.VBUS" + ], + "GND": [ + "HOST.-", + "G.GND", + "B1.GND1" + ], + "IO0": [ + "B1.IO0" + ] + } + }, + "inputs": [ + { + "at": "40ms", + "target": "B1.SW1", + "input": "pressed", + "value": true + } + ], + "expect": { + "net:IO0": { + "at": "55ms", + "max_v": 0.05 + } + } +} \ No newline at end of file diff --git a/catalog/boards/esp32_devkitc/tests/reset-button.json b/catalog/boards/esp32_devkitc/tests/reset-button.json new file mode 100644 index 000000000..c24f45f48 --- /dev/null +++ b/catalog/boards/esp32_devkitc/tests/reset-button.json @@ -0,0 +1,78 @@ +{ + "schema_version": 1, + "kind": "component_test", + "name": "reset-button", + "description": "EN (SW2) holds CHIP_PU low while pressed; C9 brings it back up through R11 once released.", + "policy": "electrical", + "until": "90ms", + "circuit": { + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc" + } + ], + "components": [ + { + "id": "HOST", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5.0 + } + }, + { + "id": "CABLE", + "component": "emblink/resistor", + "params": { + "resistance": 0.25 + } + }, + { + "id": "G", + "component": "emblink/gnd" + } + ], + "nets": { + "HOSTV": [ + "HOST.+", + "CABLE.1" + ], + "VBUS": [ + "CABLE.2", + "B1.VBUS" + ], + "GND": [ + "HOST.-", + "G.GND", + "B1.GND1" + ], + "EN": [ + "B1.EN" + ] + } + }, + "inputs": [ + { + "at": "60ms", + "target": "B1.SW2", + "input": "pressed", + "value": true + }, + { + "at": "70ms", + "target": "B1.SW2", + "input": "pressed", + "value": false + } + ], + "expect": { + "net:EN": { + "between": [ + "62ms", + "70ms" + ], + "max_v": 0.05 + } + } +} \ No newline at end of file diff --git a/catalog/boards/esp32_devkitc/tests/usb-powered.json b/catalog/boards/esp32_devkitc/tests/usb-powered.json new file mode 100644 index 000000000..f7aab7201 --- /dev/null +++ b/catalog/boards/esp32_devkitc/tests/usb-powered.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "kind": "component_test", + "name": "usb-powered", + "description": "Plugged into a computer's USB port through a 0.25 ohm cable: D2 passes VBUS to 5V less its Schottky drop, U2 (AMS1117-3.3) makes 3V3, D1 glows (about 0.6 mA through R2's 2 k) and R11 holds EN high.", + "policy": "electrical", + "until": "60ms", + "circuit": { + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc" + } + ], + "components": [ + { + "id": "HOST", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5.0 + } + }, + { + "id": "CABLE", + "component": "emblink/resistor", + "params": { + "resistance": 0.25 + } + }, + { + "id": "G", + "component": "emblink/gnd" + } + ], + "nets": { + "HOSTV": [ + "HOST.+", + "CABLE.1" + ], + "VBUS": [ + "CABLE.2", + "B1.VBUS" + ], + "GND": [ + "HOST.-", + "G.GND", + "B1.GND1" + ], + "V5": [ + "B1.5V" + ], + "V33": [ + "B1.3V3" + ], + "EN": [ + "B1.EN" + ] + } + }, + "expect": { + "net:V5": { + "at": "50ms", + "min_v": 4.5, + "max_v": 4.95 + }, + "net:V33": { + "at": "50ms", + "min_v": 3.25, + "max_v": 3.35 + }, + "net:EN": { + "at": "50ms", + "min_v": 3.2, + "max_v": 3.35 + }, + "output:B1.D1.luminance": { + "at": "50ms", + "min": 0.01 + } + } +} \ No newline at end of file diff --git a/catalog/mcus/espressif/esp32/mcu.json b/catalog/mcus/espressif/esp32/mcu.json index 62e1209d2..6bedac0a6 100644 --- a/catalog/mcus/espressif/esp32/mcu.json +++ b/catalog/mcus/espressif/esp32/mcu.json @@ -4,7 +4,7 @@ "id": "espressif/esp32", "version": "0.1.0", "name": "ESP32", - "description": "Espressif ESP32 (ESP32-D0WD-V3, chip revision v3.1): two Xtensa LX6 cores at up to 240 MHz, 520 KiB of SRAM, 448 KiB of ROM, 16 KiB of RTC memory, external SPI flash behind the cores' caches, Wi-Fi and Bluetooth.", + "description": "Espressif ESP32 (ESP32-D0WD-V3, chip revision v3.1): two Xtensa LX6 cores at up to 240 MHz, 520 KiB of SRAM, 448 KiB of ROM, 16 KiB of RTC memory, external SPI flash behind the cores' caches, Wi-Fi and Bluetooth; the ESP32-DevKitC's MCU. Espressif's ROM, ESP-IDF's second-stage bootloader and ESP-IDF applications run on both cores; the radios are not yet modelled.", "keywords": [ "xtensa", "lx6", @@ -32,7 +32,8 @@ "size": "448K", "kind": "rom", "file": "rom.bin", - "doc": "Internal ROM0 (384 KiB) and ROM1 (64 KiB) on the instruction bus (TRM v5 table 3.3-1, ESP-IDF's SOC_IROM_MASK_HIGH 0x40070000): the boot ROM of chip revision v3, its image made by tools/importers/esprom.py (--base 0x40000000 --size 0x70000 --data-table 0x4000D4EC:0x4000D4F8, the Bluetooth controller's entry; --data-table 0x4000D4F8:0x4000D5C8, the ROM's copy table of its RAM variables' initial values) from Espressif's esp32_rev300_rom.elf (espressif/esp-rom-elfs 20241011, Apache-2.0: LICENSES/Apache-2.0.txt; the ROM's code is Copyright (c) 2015-2022 Espressif Systems (Shanghai) Co. Ltd.). It equals byte for byte the image Espressif's QEMU loads (esp32-v3-rom.bin, sha256 9115bbcd...258b) over its 455,722 bytes; the rest reads zeros" + "doc": "Internal ROM0 (384 KiB) and ROM1 (64 KiB) on the instruction bus (TRM v5 table 3.3-1, ESP-IDF's SOC_IROM_MASK_HIGH 0x40070000): the boot ROM of chip revision v3, its image made by tools/importers/esprom.py (--base 0x40000000 --size 0x70000 --data-table 0x4000D4EC:0x4000D4F8, the Bluetooth controller's entry; --data-table 0x4000D4F8:0x4000D5C8, the ROM's copy table of its RAM variables' initial values) from Espressif's esp32_rev300_rom.elf (espressif/esp-rom-elfs 20241011, Apache-2.0: LICENSES/Apache-2.0.txt; the ROM's code is Copyright (c) 2015-2022 Espressif Systems (Shanghai) Co. Ltd.). It equals byte for byte the image Espressif's QEMU loads (esp32-v3-rom.bin, sha256 9115bbcd...258b) over its 455,722 bytes; the rest reads zeros", + "sha256": "ee235a76e8e5afd5bed9d674587e001d8cde459a659edef9072cbc867fd31313" }, { "name": "rom1_data", @@ -220,18 +221,36 @@ }, "fidelity": { "functional": { - "claims": "Work in progress.", - "omissions": [], - "validation": [] + "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), the internal analog I2C master, the random number generator and the peripherals' AHB view.", + "omissions": [ + "Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written)", + "the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept", + "PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts", + "the peripherals without a model yet: I2C0/1, I2S0/1, SPI2/3 (HSPI, VSPI), RMT, LEDC, MCPWM, PCNT, TWAI, SDMMC, EMAC, the SAR ADCs, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption", + "sleep modes (light and deep sleep, the power domains), brown-out", + "the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection" + ], + "validation": [ + "tests/unit/test_lx6.c: programs assembled by Espressif's assembler (integer, shifts, multiply and divide, loops, branches, memory with S32C1I, windowed recursion through the ROM's own window vectors, SYSCALL, the CCOMPARE timer interrupt, FPU, MAC16), and the APP_CPU's ROM data kept apart", + "tests/vendor/esp-idf: ESP-IDF v5.4's hello_world built unmodified for the esp32 target on the ESP32-DevKitC V4, from the ROM's banner to esp_restart", + "the ROM image equals Espressif QEMU's esp32-v3-rom.bin byte for byte, and the APP_CPU's view of it QEMU's esp32-v3-rom-app.bin" + ] }, "timed": { - "claims": "Instruction timing estimated.", - "omissions": [], + "claims": "Instruction timing estimated: one cycle, a taken branch, jump, call or return three, MULL two, a division twelve, an exception's entry three; CCOUNT counts those cycles. The clocks follow the registers: CPU_CLK from XTAL (40 MHz out of reset), the PLL's 80, 160 or 240 MHz, or RC_FAST; APB_CLK; RTC_SLOW_CLK at the RC oscillator's nominal 150 kHz.", + "omissions": [ + "the LX6 pipeline's interlocks and the loop buffer's timing", + "cache misses and flash latency (the SPI transfers' bus time is counted on SPI1, the flash's own program and erase times too)", + "the RC oscillators' error and drift" + ], "validation": [] }, "electrical": { - "claims": "GPIO pin-model defaults at 3.3 V.", - "omissions": [], + "claims": "GPIO pin-model defaults at 3.3 V from the datasheet's DC characteristics: VIH 0.75 VDD, VIL 0.25 VDD, VOH 0.8 VDD, VOL 0.1 VDD, 45 kOhm pulls; GPIO34-39 inputs only.", + "omissions": [ + "the supply current (the datasheet's power modes are not yet in the description)", + "the pads' drive strengths' effect" + ], "validation": [] } }, @@ -241,22 +260,33 @@ "sources": [ { "title": "ESP32 Technical Reference Manual", - "rev": "v5.2" + "rev": "v5" }, { "title": "ESP32 Series Datasheet", - "rev": "v4.8" + "rev": "v4" }, { "title": "Espressif esp32.svd (espressif/svd)", "rev": "be20aa1" }, { - "title": "Espressif esp-rom-elfs, esp32_rev300_rom.elf", + "title": "Espressif esp-rom-elfs, esp32_rev300_rom.elf (sha256 920b70635440517866aab2230964a570d2cf2b676658d93c52fbac108c1cca31)", "rev": "20241011" + }, + { + "title": "Espressif ESP-IDF, components/xtensa/esp32/include/xtensa/config/core-isa.h and esp32.rom.ld", + "rev": "v5.4" + }, + { + "title": "Cadence Xtensa Instruction Set Architecture Reference Manual" + }, + { + "title": "Giantec GT25Q32A-U datasheet (the module's flash)", + "rev": "V2.5" } ], - "notes": "The register description is converted from Espressif's Apache-2.0 SVD; the boot ROM's image from Espressif's Apache-2.0 ROM ELF." + "notes": "The register description is converted from Espressif's Apache-2.0 SVD; the boot ROM's image from Espressif's Apache-2.0 ROM ELF; the instruction encodings checked against Espressif's assembler." }, "signals": [ "GPIO0", diff --git a/core/mcu/mcu.c b/core/mcu/mcu.c index dc2222bab..08a3404d5 100644 --- a/core/mcu/mcu.c +++ b/core/mcu/mcu.c @@ -1167,8 +1167,8 @@ static int irq_entry(const mcu_state *st, int n) return -1; if (!strcmp(arch, "cortex-m") || !strcmp(arch, "mips")) want = n - 16; /* 16 + the vector */ - else if (!strcmp(arch, "riscv")) - return -1; + else if (!strcmp(arch, "riscv") || !strcmp(arch, "xtensa")) + return -1; /* causes and CPU interrupt numbers, not the matrix's sources */ else want = n; /* the vector itself (an AVR's, a PIC24's) */ for (i = 0; want >= 0 && i < rf->nirq; i++) diff --git a/core/mcu/xtensa/lx6.c b/core/mcu/xtensa/lx6.c index f6df523be..e08dc1f8a 100644 --- a/core/mcu/xtensa/lx6.c +++ b/core/mcu/xtensa/lx6.c @@ -57,7 +57,6 @@ * notes on the ESP32's interrupt levels and types it agrees with. */ #include #include -#include #include #include "mcu/xtensa/lx6.h" @@ -155,7 +154,6 @@ struct lx6 { int jumped; /* it branched, jumped, called or returned: no loop-back */ int waiti; /* waiting in WAITI */ u32 cost; - char xml[1]; /* no target description */ }; enum { CORE_RUN, CORE_SLEEP, CORE_HELD, CORE_STALLED }; @@ -379,10 +377,6 @@ int esk_lx6_store(struct lx6 *c, u32 a, int n, u32 v) struct esk_bus *b = &c->m->bus; struct esk_region *r; int i; -#ifdef LX6_DEBUG_TRACE - if (getenv("LX6_WATCH") && a == (u32)strtoul(getenv("LX6_WATCH"), 0, 16)) - fprintf(stderr, "WATCH core %d pc %08x writes %08x a0=%08x\n", c->id, (unsigned)c->pc, (unsigned)v, (unsigned)AR(c, 0)); -#endif if (prohibited(c->dtlb[a >> 29])) { mem_exception(c, EXC_STORE_PROHIBITED, a); return -1; @@ -1505,76 +1499,6 @@ static void exec(struct lx6 *c, u32 w, u32 len) esk_lx6_illegal(c); } -#ifdef LX6_DEBUG_TRACE -#define RING 65536 -static char ring[RING][96]; -static unsigned ring_n; -static FILE *ring_f; -static void ring_dump(void) -{ - unsigned i, n = ring_n < RING ? ring_n : RING; - for (i = 0; i < n; i++) - fputs(ring[(ring_n - n + i) % RING], ring_f); - fclose(ring_f); -} -static void lx6_debug_trace(struct lx6 *c, u32 w) -{ - static int opened; - if (!opened) { - const char *e = getenv("LX6_TRACE"); - opened = 1; - ring_f = e ? fopen(e, "w") : 0; - if (ring_f) - atexit(ring_dump); - } - { - static u32 stop_pc; - static int got; - if (!got) { - const char *e = getenv("LX6_STOP_PC"); - got = 1; - stop_pc = e ? (u32)strtoul(e, 0, 16) : 0; - } - { - static u32 logs[32]; - static int nlogs = -1; - int q; - if (nlogs < 0) { - const char *e = getenv("LX6_LOG_PCS"); - nlogs = 0; - while (e && *e && nlogs < 32) { - char *end; - logs[nlogs++] = (u32)strtoul(e, &end, 16); - e = *end ? end + 1 : end; - } - } - for (q = 0; q < nlogs; q++) - if (c->pc == logs[q]) { - char str[40]; - int z; - for (z = 0; z < 39; z++) { - u32 ch = 0; - if (esk_bus_debug_read(&c->m->bus, AR(c, 3) + (u32)z, 1, &ch) || ch < 32 || ch > 126) - break; - str[z] = (char)ch; - } - str[z] = 0; - fprintf(stderr, "PCLOG core %d at %08x cycles %llu a0=%08x a2=%08x a3=%08x a4=%08x s3=%s\n", c->id, - (unsigned)c->pc, (unsigned long long)c->m->cycles, (unsigned)AR(c, 0), (unsigned)AR(c, 2), - (unsigned)AR(c, 3), (unsigned)AR(c, 4), str); - } - } - if (stop_pc && c->pc == stop_pc) { - fprintf(stderr, "LX6_STOP_PC hit on core %d\n", c->id); - exit(0); - } - } - if (ring_f) - snprintf(ring[ring_n++ % RING], 96, "%d %08x %06x a0=%08x a1=%08x a2=%08x a3=%08x\n", c->id, - (unsigned)c->pc, (unsigned)w, (unsigned)AR(c, 0), (unsigned)AR(c, 1), (unsigned)AR(c, 2), - (unsigned)AR(c, 3)); -} -#endif /* ---- interrupts and a step -------------------------------------------- */ @@ -1653,9 +1577,6 @@ static void core_step(struct lx6 *c) c->npc = c->pc + len; c->jumped = 0; m->insns++; -#ifdef LX6_DEBUG_TRACE - lx6_debug_trace(c, w); -#endif if ((w & 15u) >= 14u) esk_lx6_illegal(c); else @@ -1902,7 +1823,6 @@ static u32 get(const u8 *b) static u32 *reg_ptr(struct lx6 *c, int n) { - static u32 zero; if (n == GR_PC) return &c->pc; if (n >= GR_AR && n < GR_AR + 64) @@ -1928,8 +1848,13 @@ static u32 *reg_ptr(struct lx6 *c, int n) case GR_FCR: return &c->fcr; case GR_FSR: return &c->fsr; } - zero = 0; - return n == 71 || n == 72 || (n >= 83 && n <= 86) ? &zero : 0; + switch (n) { + case 83: return &c->expstate; + case 84: return &c->f64r_lo; + case 85: return &c->f64r_hi; + case 86: return &c->f64s; + } + return 0; } static int core_reg_read(struct lx6 *c, int n, u8 *buf) diff --git a/docs/STATUS.md b/docs/STATUS.md index a8394f400..d43415860 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -5,6 +5,21 @@ maintained by hand until evidence tooling exists. It is not a generated support matrix. Specifications define target contracts; their acceptance does not mean the described behaviour exists. +## ESP32 on the machine framework (2026-11-08) + +The ESP32 now runs as a catalog MCU (`espressif/esp32`) on the +`emblink/esp32_devkitc` board: two Xtensa LX6 cores in the framework +(`core/mcu/xtensa/lx6.c`), the SoC models in `core/mcu/esp32/soc/`, and +Espressif's ROM (ECO3), ESP-IDF v5.4's second-stage bootloader and its +hello_world running unmodified on both cores at 160 MHz to `Hello world!` +and through `esp_restart` (rst:0xc SW_CPU_RESET). Evidence: `test_lx6` +(4,332 checks), the board's three fixtures, and the ESP-IDF vendor +scenario `esp32_hello_world` (9/9). Not yet: Wi-Fi and Bluetooth, the +double-precision accelerator's instructions, the peripherals the +description's fidelity lists (I2C, I2S, SPI2/3, RMT, LEDC, ADC, ...), +sleep. The standalone harness below remains as its own qualification +fixtures. + ## Original ESP32 dual-core target investigation (2026-11-04) **Latest digital pad-hold update:** [Per-pin digital hold](engineering/esp32-digital-pad-hold.md) diff --git a/docs/coverage.md b/docs/coverage.md index b112be32e..3c4dc6e27 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -8,6 +8,7 @@ evidence for each model is in [STATUS.md](STATUS.md). | MCU | Modelled | Registers only | Missing | Peripherals | |---|---|---|---|---| +| ESP32 | 14 | 0 | 31 | 45 | | ESP32-C3 | 35 | 0 | 0 | 35 | | ATmega328P | 15 | 0 | 0 | 15 | | PIC24FJ1024GB610 | 11 | 4 | 17 | 32 | @@ -19,6 +20,61 @@ evidence for each model is in [STATUS.md](STATUS.md). | STM32F446RE | 53 | 0 | 15 | 68 | | STM32F446ZE | 53 | 0 | 15 | 68 | +## ESP32 (`espressif/esp32`) + +Boards: ESP32-DevKitC V4. +14 of 45 peripherals modelled, 0 registers only, 31 missing. + +| Peripheral | Model | State | +|---|---|---| +| DPORT | `esp32-dport` | modelled | +| AES | — | **missing** | +| RSA | — | **missing** | +| SHA | `esp32-sha` | modelled | +| UART0 | `esp32-uart` | modelled | +| SPI1 | `esp32-spi` | modelled | +| SPI0 | `esp32-spi` | modelled | +| GPIO | `esp32-gpio` | modelled | +| GPIO_SD | — | **missing** | +| FLASH_ENCRYPTION | — | **missing** | +| FRC_TIMER | — | **missing** | +| RTC_CNTL | `esp32-rtc` | modelled | +| RTC_IO | — | **missing** | +| SENS | — | **missing** | +| RTC_I2C | — | **missing** | +| IO_MUX | `esp32-iomux` | modelled | +| HINF | — | **missing** | +| UHCI1 | — | **missing** | +| I2S0 | — | **missing** | +| UART1 | `esp32-uart` | modelled | +| I2C0 | — | **missing** | +| UHCI0 | — | **missing** | +| SLCHOST | — | **missing** | +| RMT | — | **missing** | +| PCNT | — | **missing** | +| SLC | — | **missing** | +| LEDC | — | **missing** | +| EFUSE | `esp32-efuse` | modelled | +| NRX | — | **missing** | +| BB | — | **missing** | +| MCPWM0 | — | **missing** | +| TIMG0 | `esp32-timg` | modelled | +| TIMG1 | `esp32-timg` | modelled | +| SPI2 | — | **missing** | +| SPI3 | — | **missing** | +| APB_CTRL | `esp32-apbctrl` | modelled | +| I2C1 | — | **missing** | +| SDHOST | — | **missing** | +| EMAC_DMA | — | **missing** | +| EMAC_EXT | — | **missing** | +| EMAC_MAC | — | **missing** | +| TWAI0 | — | **missing** | +| MCPWM1 | — | **missing** | +| I2S1 | — | **missing** | +| UART2 | `esp32-uart` | modelled | + +Declared omissions: Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written); the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept; PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts; the peripherals without a model yet: I2C0/1, I2S0/1, SPI2/3 (HSPI, VSPI), RMT, LEDC, MCPWM, PCNT, TWAI, SDMMC, EMAC, the SAR ADCs, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption; sleep modes (light and deep sleep, the power domains), brown-out; the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection. + ## ESP32-C3 (`espressif/esp32c3`) Boards: ESP32-C3-DevKitM-1. diff --git a/tests/unit/gen_lx6_programs.py b/tests/unit/gen_lx6_programs.py new file mode 100644 index 000000000..7e2bbb162 --- /dev/null +++ b/tests/unit/gen_lx6_programs.py @@ -0,0 +1,294 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Junior Deogracias +"""Assemble test_lx6.c's programs with Espressif's Xtensa assembler and +write lx6_programs.h, so the test needs no toolchain. + + gen_lx6_programs.py --as xtensa-esp32-elf-as --objcopy xtensa-esp32-elf-objcopy + +Each program is linked at 0x40080000 (IRAM), its data at 0x3FFB0000. +The sources are the PROGRAMS below; their expected results are in +test_lx6.c, computed by hand from the Xtensa ISA reference.""" +import argparse +import subprocess +import sys +import tempfile +from pathlib import Path + +HERE = Path(__file__).resolve().parent + +PROGRAMS = { + # integer arithmetic and logic: results left in a2-a15 + "alu": """ + movi a2, 7 + movi a3, -3 + add a4, a2, a3 # 4 + addx4 a5, a2, a3 # 25 + subx8 a6, a2, a3 # 59 + neg a7, a3 # 3 + abs a8, a3 # 3 + min a9, a2, a3 # -3 + maxu a10, a2, a3 # 0xfffffffd + sext a11, a3, 7 # -3 + movi a12, 300 + clamps a12, a12, 7 # 127 + nsau a13, a2 # 29 + nsa a14, a3 # 29 + extui a15, a3, 4, 8 # 0xff + j . +""", + "shifts": """ + movi a2, 0x123 + slli a3, a2, 20 # 0x12300000 + srli a4, a3, 8 # 0x00123000 + movi a5, -256 + srai a6, a5, 4 # -16 + ssl a2 # shift left by a2 & 31 = 3 + sll a7, a2 # 0x918 + movi a8, 12 + ssr a8 + srl a9, a3 # 0x00012300 + sra a10, a5 # -1 + movi a11, 0x87654321 + ssai 8 + src a12, a11, a2 # (0x87654321:0x123) >> 8 = 0x21000001 + ssa8l a8 # sar = (12 & 3) * 8 = 0 + rsr.sar a13 # 0 + ssa8b a2 # sar = 32 - (0x123 & 3) * 8 = 8 + rsr.sar a14 # 8 + j . +""", + "muldiv": """ + movi a2, -7 + movi a3, 3 + mull a4, a2, a3 # -21 + muluh a5, a2, a3 # 2 + mulsh a6, a2, a3 # -1 + quos a7, a2, a3 # -2 + rems a8, a2, a3 # -1 + quou a9, a2, a3 # 0x55555553 + remu a10, a2, a3 # 0 + mul16u a11, a2, a3 # 0xfff9 * 3 = 0x2ffeb + mul16s a12, a2, a3 # -21 + movi a13, 0 + j . +""", + # a loop exited by a branch taken to LEND (the ROM's strcmp does it) + "loops": """ + movi a2, 0 + movi a3, 5 + loop a3, 1f + addi a2, a2, 1 +1: + movi a4, 0 + movi a5, 0 + loopnez a5, 2f + addi a4, a4, 1 +2: + movi a6, 0 + movi a7, 10 + movi a8, 0 + loop a8, 3f # LCOUNT 0xffffffff: only the branch leaves + addi a6, a6, 1 + addi a7, a7, -1 + beqz a7, 3f +3: + movi a9, 0 + movi a10, -2 + loopgtz a10, 4f + addi a9, a9, 1 +4: + j . +""", + "branches": """ + movi a2, 0 + movi a3, 0x80 + bbci a3, 7, 1f + addi a2, a2, 1 # taken path skips nothing: bit 7 is set +1: bbsi a3, 7, 2f + addi a2, a2, 16 # not reached +2: movi a4, 65536 + bltui a4, 32768, 3f + addi a2, a2, 2 # reached: 65536 >= 32768 +3: bgeui a4, 65536, 4f + addi a2, a2, 32 # not reached +4: movi a5, -1 + beqi a5, -1, 5f + addi a2, a2, 64 # not reached +5: movi a6, 0xf0 + movi a7, 0x30 + ball a6, a7, 6f + addi a2, a2, 128 # not reached +6: bnone a6, a7, 7f + addi a2, a2, 4 # reached +7: movi a8, 5 + bbs a3, a8, 8f # bit 5 of 0x80: clear + addi a2, a2, 8 # reached +8: j . +""", + "memory": """ + j 1f + .align 4 +lit: .word 0xcafef00d +1: + movi a2, 0x3ffb0000 + movi a3, 0x8081 + s16i a3, a2, 0 + l16si a4, a2, 0 # 0xffff8081 + l16ui a5, a2, 0 # 0x8081 + l8ui a6, a2, 1 # 0x80 + movi a7, 0x11223344 + s32i a7, a2, 4 + addi a15, a2, 3 + l32i a8, a15, 0 # unaligned: bytes 3..6, 0x22334400 + movi a9, 0x55 + wsr.scompare1 a9 + movi a10, 0x55 + s32i a10, a2, 8 + movi a11, 0x66 + s32c1i a11, a2, 8 # memory 0x55 == SCOMPARE1: stored; a11 = 0x55 + l32i a12, a2, 8 # 0x66 + movi a13, 0x77 + s32c1i a13, a2, 8 # 0x66 != 0x55: not stored; a13 = 0x66 + l32r a14, lit + j . +""", + # windowed calls deep enough to spill and fill through the ROM's vectors + "windows": """ + movi a1, 0x3ffb8000 + movi a3, 0x3ffb8100 # the outermost frame's caller's stack pointer + addi a4, a1, -16 + s32i a3, a4, 4 # at sp - 12, for WindowOverflow8 to find its save area + movi a10, 12 + call8 fib + mov a15, a10 + j . + .align 4 +fib: + entry a1, 32 + bltui a2, 2, 1f + addi a10, a2, -1 + call8 fib + mov a3, a10 + addi a10, a2, -2 + call8 fib + add a2, a3, a10 +1: retw +""", + # an exception and its return, through a vector table of the test's + "syscall": """ + movi a2, vectors + wsr.vecbase a2 + movi a3, 0 + syscall + addi a3, a3, 1 + j . + .align 1024 +vectors: + .org vectors + 0x300 +kernel: + rsr.exccause a4 + rsr.epc1 a5 + addi a5, a5, 3 + wsr.epc1 a5 + rfe +""", + # CCOMPARE0 against CCOUNT: the timer interrupt (6) at level 1 + "timer": """ + movi a2, vectors + wsr.vecbase a2 + movi a6, 0 + rsr.ccount a3 + addi a3, a3, 200 + wsr.ccompare0 a3 + movi a4, 1 << 6 + wsr.intenable a4 + rsil a5, 0 +1: beqz a6, 1b + j . + .align 1024 +vectors: + .org vectors + 0x300 +kernel: + rsr.exccause a7 + rsr.interrupt a8 + movi a6, 1 + movi a9, 0 + wsr.intenable a9 + rfe +""", + "float": """ + movi a2, 1 + wsr.cpenable a2 + rsync + movi a3, 3 + float.s f0, a3, 0 # 3.0 + movi a4, 5 + float.s f1, a4, 1 # 2.5 + add.s f2, f0, f1 # 5.5 + mul.s f3, f0, f1 # 7.5 + madd.s f2, f0, f1 # 5.5 + 7.5 = 13.0 + trunc.s a5, f2, 0 # 13 + round.s a6, f1, 0 # 2 (ties to even) + olt.s b0, f1, f0 + movi a7, 0 + movt a7, a4, b0 # 5: 2.5 < 3.0 + rfr a8, f3 # 0x40f00000 + j . +""", + "mac16": """ + movi a2, 0x00030004 + movi a3, 0xfffe0005 + mul.aa.ll a2, a3 # 4 * 5 = 20 + mula.aa.hh a2, a3 # + 3 * -2 = 14 + rsr.acclo a4 + rsr.acchi a5 + umul.aa.hl a2, a3 # 3 * 5 = 15 + rsr.acclo a6 + j . +""", +} + + +def main(argv=None) -> int: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--as", dest="asm", default="xtensa-esp32-elf-as") + ap.add_argument("--ld", default="xtensa-esp32-elf-ld") + ap.add_argument("--objcopy", default="xtensa-esp32-elf-objcopy") + ap.add_argument("--nm", default="xtensa-esp32-elf-nm") + ap.add_argument("-o", "--output", type=Path, default=HERE / "lx6_programs.h") + args = ap.parse_args(argv) + out = ["/* SPDX-License-Identifier: Apache-2.0 */", + "/* Copyright 2026 Junior Deogracias */", + "/* lx6_programs.h -- test_lx6.c's programs, assembled by Espressif's", + " * xtensa-esp32-elf-as and linked at 0x40080000 by gen_lx6_programs.py", + " * (generated: do not edit; the sources are in the script). */", + "#ifndef LX6_PROGRAMS_H", "#define LX6_PROGRAMS_H", ""] + with tempfile.TemporaryDirectory() as tmp: + t = Path(tmp) + for name, src in PROGRAMS.items(): + s = t / f"{name}.S" + s.write_text(".text\n.literal_position\n.global _start\n_start:\n" + src, encoding="utf-8") + subprocess.run([args.asm, "--text-section-literals", "-o", str(t / f"{name}.o"), str(s)], check=True) + subprocess.run([args.ld, "-Ttext=0x40080000", "-o", str(t / f"{name}.elf"), str(t / f"{name}.o")], + check=True) + subprocess.run([args.objcopy, "-O", "binary", "-j", ".text", str(t / f"{name}.elf"), + str(t / f"{name}.bin")], check=True) + data = (t / f"{name}.bin").read_bytes() + syms = subprocess.run([args.nm, str(t / f"{name}.elf")], check=True, capture_output=True, + text=True).stdout.split("\n") + entry = next(int(x.split()[0], 16) for x in syms if x.endswith(" _start")) + out.append(f"#define PROG_{name.upper()}_ENTRY 0x{entry:08X}u") + out.append(f"static const unsigned char prog_{name}[{len(data)}] = {{") + for i in range(0, len(data), 16): + out.append(" " + " ".join(f"0x{b:02x}," for b in data[i:i + 16])) + out.append("};") + out.append("") + out += ["#endif", ""] + args.output.write_text("\n".join(out), encoding="utf-8") + print(f"{args.output}: {len(PROGRAMS)} programs") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/unit/lx6_programs.h b/tests/unit/lx6_programs.h new file mode 100644 index 000000000..8326b4dc3 --- /dev/null +++ b/tests/unit/lx6_programs.h @@ -0,0 +1,312 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* lx6_programs.h -- test_lx6.c's programs, assembled by Espressif's + * xtensa-esp32-elf-as and linked at 0x40080000 by gen_lx6_programs.py + * (generated: do not edit; the sources are in the script). */ +#ifndef LX6_PROGRAMS_H +#define LX6_PROGRAMS_H + +#define PROG_ALU_ENTRY 0x40080000u +static const unsigned char prog_alu[45] = { + 0x0c, 0x72, 0x7c, 0xd3, 0x3a, 0x42, 0x30, 0x52, 0xa0, 0x30, 0x62, 0xf0, 0x30, 0x70, 0x60, 0x30, + 0x81, 0x60, 0x30, 0x92, 0x43, 0x30, 0xa2, 0x73, 0x00, 0xb3, 0x23, 0xc2, 0xa1, 0x2c, 0x00, 0xcc, + 0x33, 0xd0, 0xf2, 0x40, 0xe0, 0xe3, 0x40, 0x30, 0xf4, 0x74, 0x06, 0xff, 0xff, +}; + +#define PROG_SHIFTS_ENTRY 0x40080004u +static const unsigned char prog_shifts[60] = { + 0x21, 0x43, 0x65, 0x87, 0x22, 0xa1, 0x23, 0xc0, 0x32, 0x01, 0x30, 0x48, 0x41, 0x52, 0xaf, 0x00, + 0x50, 0x64, 0x21, 0x00, 0x12, 0x40, 0x00, 0x72, 0xa1, 0x0c, 0xc8, 0x00, 0x08, 0x40, 0x30, 0x90, + 0x91, 0x50, 0xa0, 0xb1, 0xb1, 0xf7, 0xff, 0x00, 0x48, 0x40, 0x20, 0xcb, 0x81, 0x00, 0x28, 0x40, + 0xd0, 0x03, 0x03, 0x00, 0x32, 0x40, 0xe0, 0x03, 0x03, 0x06, 0xff, 0xff, +}; + +#define PROG_MULDIV_ENTRY 0x40080000u +static const unsigned char prog_muldiv[36] = { + 0x7c, 0x92, 0x0c, 0x33, 0x30, 0x42, 0x82, 0x30, 0x52, 0xa2, 0x30, 0x62, 0xb2, 0x30, 0x72, 0xd2, + 0x30, 0x82, 0xf2, 0x30, 0x92, 0xc2, 0x30, 0xa2, 0xe2, 0x30, 0xb2, 0xc1, 0x30, 0xc2, 0xd1, 0x0c, + 0x0d, 0x06, 0xff, 0xff, +}; + +#define PROG_LOOPS_ENTRY 0x40080000u +static const unsigned char prog_loops[47] = { + 0x0c, 0x02, 0x32, 0xa0, 0x05, 0x76, 0x83, 0x01, 0x1b, 0x22, 0x0c, 0x04, 0x0c, 0x05, 0x76, 0x95, + 0x01, 0x1b, 0x44, 0x0c, 0x06, 0x0c, 0xa7, 0x0c, 0x08, 0x76, 0x88, 0x06, 0x1b, 0x66, 0x0b, 0x77, + 0x16, 0xf7, 0xff, 0x0c, 0x09, 0x7c, 0xea, 0x76, 0xaa, 0x01, 0x1b, 0x99, 0x06, 0xff, 0xff, +}; + +#define PROG_BRANCHES_ENTRY 0x40080004u +static const unsigned char prog_branches[76] = { + 0x00, 0x00, 0x01, 0x00, 0x22, 0xa0, 0x00, 0x32, 0xa0, 0x80, 0x77, 0x63, 0x02, 0x22, 0xc2, 0x01, + 0x77, 0xe3, 0x02, 0x22, 0xc2, 0x10, 0x41, 0xfa, 0xff, 0xb6, 0x04, 0x01, 0x2b, 0x22, 0xf6, 0x14, + 0x02, 0x22, 0xc2, 0x20, 0x7c, 0xf5, 0x26, 0x05, 0x02, 0x22, 0xc2, 0x40, 0x62, 0xa0, 0xf0, 0x3c, + 0x07, 0x77, 0x46, 0x05, 0x22, 0xd2, 0x01, 0x22, 0xc2, 0x80, 0x77, 0x06, 0x02, 0x22, 0xc2, 0x04, + 0x0c, 0x58, 0x87, 0xd3, 0x02, 0x22, 0xc2, 0x08, 0x06, 0xff, 0xff, 0x00, +}; + +#define PROG_MEMORY_ENTRY 0x4008000Cu +static const unsigned char prog_memory[76] = { + 0x00, 0x00, 0xfb, 0x3f, 0x81, 0x80, 0x00, 0x00, 0x44, 0x33, 0x22, 0x11, 0x06, 0x01, 0x00, 0x00, + 0x0d, 0xf0, 0xfe, 0xca, 0x21, 0xfb, 0xff, 0x31, 0xfb, 0xff, 0x32, 0x52, 0x00, 0x42, 0x92, 0x00, + 0x52, 0x12, 0x00, 0x62, 0x02, 0x01, 0x71, 0xf8, 0xff, 0x79, 0x12, 0x3b, 0xf2, 0x88, 0x0f, 0x5c, + 0x59, 0x90, 0x0c, 0x13, 0x5c, 0x5a, 0xa9, 0x22, 0xb2, 0xa0, 0x66, 0xb2, 0xe2, 0x02, 0xc8, 0x22, + 0xd2, 0xa0, 0x77, 0xd2, 0xe2, 0x02, 0xe1, 0xf2, 0xff, 0x06, 0xff, 0xff, +}; + +#define PROG_WINDOWS_ENTRY 0x40080008u +static const unsigned char prog_windows[56] = { + 0x00, 0x80, 0xfb, 0x3f, 0x00, 0x81, 0xfb, 0x3f, 0x11, 0xfe, 0xff, 0x31, 0xfe, 0xff, 0x42, 0xc1, + 0xf0, 0x39, 0x14, 0x0c, 0xca, 0xa5, 0x00, 0x00, 0xfd, 0x0a, 0x06, 0xff, 0xff, 0x00, 0x00, 0x00, + 0x36, 0x41, 0x00, 0xb6, 0x22, 0x0f, 0xa2, 0xc2, 0xff, 0x65, 0xff, 0xff, 0x3d, 0x0a, 0xa2, 0xc2, + 0xfe, 0xe5, 0xfe, 0xff, 0xaa, 0x23, 0x1d, 0xf0, +}; + +#define PROG_SYSCALL_ENTRY 0x40080004u +static const unsigned char prog_syscall[1808] = { + 0x00, 0x04, 0x08, 0x40, 0x21, 0xff, 0xff, 0x20, 0xe7, 0x13, 0x0c, 0x03, 0x00, 0x50, 0x00, 0x1b, + 0x33, 0x06, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0xe8, 0x03, 0x50, 0xb1, 0x03, 0x3b, 0x55, 0x50, 0xb1, 0x13, 0x00, 0x30, 0x00, 0x00, 0x00, +}; + +#define PROG_TIMER_ENTRY 0x40080004u +static const unsigned char prog_timer[1808] = { + 0x00, 0x04, 0x08, 0x40, 0x21, 0xff, 0xff, 0x20, 0xe7, 0x13, 0x0c, 0x06, 0x30, 0xea, 0x03, 0x32, + 0xd3, 0x01, 0x32, 0xc3, 0xc8, 0x30, 0xf0, 0x13, 0x4c, 0x04, 0x40, 0xe4, 0x13, 0x50, 0x60, 0x00, + 0x16, 0xc6, 0xff, 0x06, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x70, 0xe8, 0x03, 0x80, 0xe2, 0x03, 0x0c, 0x16, 0x0c, 0x09, 0x90, 0xe4, 0x13, 0x00, 0x30, 0x00, +}; + +#define PROG_FLOAT_ENTRY 0x40080000u +static const unsigned char prog_float[47] = { + 0x0c, 0x12, 0x20, 0xe0, 0x13, 0x10, 0x20, 0x00, 0x0c, 0x33, 0x00, 0x03, 0xca, 0x0c, 0x54, 0x10, + 0x14, 0xca, 0x10, 0x20, 0x0a, 0x10, 0x30, 0x2a, 0x10, 0x20, 0x4a, 0x00, 0x52, 0x9a, 0x00, 0x61, + 0x8a, 0x00, 0x01, 0x4b, 0x0c, 0x07, 0x00, 0x74, 0xd3, 0x40, 0x83, 0xfa, 0x06, 0xff, 0xff, +}; + +#define PROG_MAC16_ENTRY 0x40080008u +static const unsigned char prog_mac16[36] = { + 0x04, 0x00, 0x03, 0x00, 0x05, 0x00, 0xfe, 0xff, 0x21, 0xfe, 0xff, 0x31, 0xfe, 0xff, 0x34, 0x02, + 0x74, 0x34, 0x02, 0x7b, 0x40, 0x10, 0x03, 0x50, 0x11, 0x03, 0x34, 0x02, 0x71, 0x60, 0x10, 0x03, + 0x06, 0xff, 0xff, 0x00, +}; + +#endif diff --git a/tests/unit/test_lx6.c b/tests/unit/test_lx6.c new file mode 100644 index 000000000..79436eb8a --- /dev/null +++ b/tests/unit/test_lx6.c @@ -0,0 +1,351 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* test_lx6.c -- the ESP32's Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) + * on the machine the catalog's ESP32 description builds: programs + * assembled by Espressif's assembler (lx6_programs.h, gen_lx6_programs.py) + * run from IRAM, their results checked against the Xtensa ISA reference + * worked by hand. The window spills and fills of the recursive program go + * through the ESP32 ROM's own window vectors. */ +#include "harness.h" +#include "lx6_programs.h" +#include "mcu/build.h" +#include "mcu/xtensa/lx6.h" +#include "util/json.h" + +#define IRAM 0x40080000u +#define GR_PC 0 +#define GR_WB 69 +#define GR_WS 70 +#define GR_PS 73 +#define GR_A 105 +#define J_SELF 0xffff06u + +typedef struct t32 { + esk_alloc a; + esk_json *mcu, *regs; + struct esk_mach m; +} t32; + +static unsigned char *slurp(const char *rel, size_t *len) +{ + char path[1024]; + FILE *f; + unsigned char *buf; + long n; + snprintf(path, sizeof path, "%s/%s", EMBSIM_SOURCE_DIR, rel); + f = fopen(path, "rb"); + if (!f) + return NULL; + fseek(f, 0, SEEK_END); + n = ftell(f); + fseek(f, 0, SEEK_SET); + buf = (unsigned char *)malloc(n > 0 ? (size_t)n : 1); + if (buf && fread(buf, 1, (size_t)n, f) != (size_t)n) { + free(buf); + buf = NULL; + } + fclose(f); + *len = (size_t)n; + return buf; +} + +static esk_json *load_json(esk_alloc *a, const char *rel) +{ + size_t len = 0; + unsigned char *text = slurp(rel, &len); + esk_json *j; + int line = 0; + const char *err = NULL; + if (!text) + return NULL; + j = esk_json_parse(a, (const char *)text, len, &line, &err); + free(text); + return j; +} + +static void put32(u8 *b, u32 v) +{ + b[0] = (u8)v; + b[1] = (u8)(v >> 8); + b[2] = (u8)(v >> 16); + b[3] = (u8)(v >> 24); +} + +static u32 reg(t32 *t, int n) +{ + u8 b[8] = { 0 }; + t->m.cpu->ops->reg_read(t->m.cpu, n, b); + return (u32)b[0] | (u32)b[1] << 8 | (u32)b[2] << 16 | (u32)b[3] << 24; +} + +static void set_reg(t32 *t, int n, u32 v) +{ + u8 b[4]; + put32(b, v); + CHECK_EQ(t->m.cpu->ops->reg_write(t->m.cpu, n, b), 4); +} + +static u32 a(t32 *t, int n) { return reg(t, GR_A + n); } + +/* the ESP32 out of reset, `prog` in IRAM, the PRO_CPU at `entry` with + * windowed calls on (PS.WOE), interrupts open, a stack in DRAM */ +static int open32(t32 *t, const unsigned char *prog, size_t n, u32 entry) +{ + char why[200]; + es_status st; + size_t i; + memset(t, 0, sizeof *t); + esk_alloc_init(&t->a, NULL); + t->mcu = load_json(&t->a, "catalog/mcus/espressif/esp32/mcu.json"); + t->regs = load_json(&t->a, "catalog/mcus/espressif/esp32/registers.json"); + CHECK(t->mcu != NULL && t->regs != NULL); + if (!t->mcu || !t->regs) + return 0; + esk_mach_init(&t->m, &t->a); + st = esk_mach_build(&t->m, t->mcu, t->regs, 40000000u, why, sizeof why); + if (st != ES_OK) { + FAIL(why); + return 0; + } + { + size_t rlen = 0; + unsigned char *rom = slurp("catalog/mcus/espressif/esp32/rom.bin", &rlen); + CHECK(rom != NULL); + if (!rom) + return 0; + CHECK_EQ(esk_mach_factory(&t->m, 0x40000000u, rom, rlen), ES_OK); + free(rom); + } + esk_mach_reset(&t->m); + CHECK(esk_lx6_is(t->m.cpu)); + CHECK_STR(t->m.cpu->ops->name, "xtensa"); + CHECK_EQ(esk_lx6_cores(&t->m), 2); + CHECK_EQ(reg(t, GR_PC), 0x40000400u); /* the reset vector */ + for (i = 0; i < n; i++) + CHECK_EQ(esk_bus_debug_write(&t->m.bus, IRAM + (u32)i, 1, prog[i]), 0); + set_reg(t, GR_PC, entry); + set_reg(t, GR_PS, 0x00040000u); /* WOE, INTLEVEL 0 */ + set_reg(t, GR_WB, 0); + set_reg(t, GR_WS, 1); + set_reg(t, GR_A + 1, 0x3FFB8000u); + return 1; +} + +static void close32(t32 *t) +{ + esk_mach_free(&t->m); + esk_json_free(&t->a, t->mcu); + esk_json_free(&t->a, t->regs); + CHECK_EQ(t->a.live_bytes, 0); +} + +/* runs until the program's final `j .`: 1 when it got there */ +static int run_to_end(t32 *t, u64 max_insns) +{ + u32 w = 0; + esk_mach_run(&t->m, 0xffffffffu, max_insns); + if (esk_bus_debug_read(&t->m.bus, reg(t, GR_PC), 4, &w)) + return 0; + return (w & 0xffffffu) == J_SELF; +} + +#define OPEN(t, name, NAME) open32(&(t), prog_##name, sizeof prog_##name, PROG_##NAME##_ENTRY) + +static void test_alu(void) +{ + t32 t; + if (!OPEN(t, alu, ALU)) + return; + CHECK(run_to_end(&t, 100)); + CHECK_EQ(a(&t, 4), 4u); + CHECK_EQ(a(&t, 5), 25u); + CHECK_EQ(a(&t, 6), 59u); + CHECK_EQ(a(&t, 7), 3u); + CHECK_EQ(a(&t, 8), 3u); + CHECK_EQ(a(&t, 9), (u32)-3); + CHECK_EQ(a(&t, 10), 0xfffffffdu); + CHECK_EQ(a(&t, 11), (u32)-3); + CHECK_EQ(a(&t, 12), 127u); + CHECK_EQ(a(&t, 13), 29u); + CHECK_EQ(a(&t, 14), 29u); + CHECK_EQ(a(&t, 15), 0xffu); + close32(&t); +} + +static void test_shifts(void) +{ + t32 t; + if (!OPEN(t, shifts, SHIFTS)) + return; + CHECK(run_to_end(&t, 100)); + CHECK_EQ(a(&t, 3), 0x12300000u); + CHECK_EQ(a(&t, 4), 0x00123000u); + CHECK_EQ(a(&t, 6), (u32)-16); + CHECK_EQ(a(&t, 7), 0x918u); + CHECK_EQ(a(&t, 9), 0x12300u); + CHECK_EQ(a(&t, 10), 0xffffffffu); + CHECK_EQ(a(&t, 12), 0x21000001u); + CHECK_EQ(a(&t, 13), 0u); + CHECK_EQ(a(&t, 14), 8u); + close32(&t); +} + +static void test_muldiv(void) +{ + t32 t; + if (!OPEN(t, muldiv, MULDIV)) + return; + CHECK(run_to_end(&t, 100)); + CHECK_EQ(a(&t, 4), (u32)-21); + CHECK_EQ(a(&t, 5), 2u); + CHECK_EQ(a(&t, 6), 0xffffffffu); + CHECK_EQ(a(&t, 7), (u32)-2); + CHECK_EQ(a(&t, 8), (u32)-1); + CHECK_EQ(a(&t, 9), 0x55555553u); + CHECK_EQ(a(&t, 10), 0u); + CHECK_EQ(a(&t, 11), 0x2ffebu); + CHECK_EQ(a(&t, 12), (u32)-21); + close32(&t); +} + +/* the zero-overhead loops, one left by a branch taken to its end */ +static void test_loops(void) +{ + t32 t; + if (!OPEN(t, loops, LOOPS)) + return; + CHECK(run_to_end(&t, 200)); + CHECK_EQ(a(&t, 2), 5u); + CHECK_EQ(a(&t, 4), 0u); /* LOOPNEZ of 0: skipped */ + CHECK_EQ(a(&t, 6), 10u); /* left by BEQZ to LEND, not looped again */ + CHECK_EQ(a(&t, 7), 0u); + CHECK_EQ(a(&t, 9), 0u); /* LOOPGTZ of -2: skipped */ + close32(&t); +} + +static void test_branches(void) +{ + t32 t; + if (!OPEN(t, branches, BRANCHES)) + return; + CHECK(run_to_end(&t, 100)); + CHECK_EQ(a(&t, 2), 15u); + close32(&t); +} + +static void test_memory(void) +{ + t32 t; + if (!OPEN(t, memory, MEMORY)) + return; + CHECK(run_to_end(&t, 100)); + CHECK_EQ(a(&t, 4), 0xffff8081u); + CHECK_EQ(a(&t, 5), 0x8081u); + CHECK_EQ(a(&t, 6), 0x80u); + CHECK_EQ(a(&t, 8), 0x22334400u); + CHECK_EQ(a(&t, 11), 0x55u); /* S32C1I stored, and returned the old word */ + CHECK_EQ(a(&t, 12), 0x66u); + CHECK_EQ(a(&t, 13), 0x66u); /* compare failed: nothing stored */ + CHECK_EQ(a(&t, 14), 0xcafef00du); + close32(&t); +} + +/* fib(12) by CALL8/ENTRY/RETW: twelve frames deep, spilled and filled by + * the ROM's WindowOverflow8 and WindowUnderflow8 */ +static void test_windows(void) +{ + t32 t; + if (!OPEN(t, windows, WINDOWS)) + return; + CHECK(run_to_end(&t, 200000)); + CHECK_EQ(a(&t, 15), 144u); + CHECK_EQ(reg(&t, GR_WB), 0u); + close32(&t); +} + +static void test_syscall(void) +{ + t32 t; + if (!OPEN(t, syscall, SYSCALL)) + return; + CHECK(run_to_end(&t, 100)); + CHECK_EQ(a(&t, 4), 1u); /* EXCCAUSE: SyscallCause */ + CHECK_EQ(a(&t, 3), 1u); /* back after it */ + CHECK_EQ(reg(&t, GR_PS) & 0x10u, 0u); /* RFE cleared EXCM */ + close32(&t); +} + +static void test_timer(void) +{ + t32 t; + if (!OPEN(t, timer, TIMER)) + return; + CHECK(run_to_end(&t, 2000)); + CHECK_EQ(a(&t, 6), 1u); + CHECK_EQ(a(&t, 7), 4u); /* Level1InterruptCause */ + CHECK_EQ(a(&t, 8) & 0x40u, 0x40u); /* INTERRUPT: timer 0 */ + close32(&t); +} + +static void test_float(void) +{ + t32 t; + if (!OPEN(t, float, FLOAT)) + return; + CHECK(run_to_end(&t, 100)); + CHECK_EQ(a(&t, 5), 13u); + CHECK_EQ(a(&t, 6), 2u); + CHECK_EQ(a(&t, 7), 5u); + CHECK_EQ(a(&t, 8), 0x40f00000u); + close32(&t); +} + +static void test_mac16(void) +{ + t32 t; + if (!OPEN(t, mac16, MAC16)) + return; + CHECK(run_to_end(&t, 100)); + CHECK_EQ(a(&t, 4), 14u); + CHECK_EQ(a(&t, 5), 0u); + CHECK_EQ(a(&t, 6), 15u); + close32(&t); +} + +/* the APP_CPU's ROM keeps its own data: released, it runs the ROM's start, + * which sets g_ticks_per_us_app (0x3FFE40F0) and leaves the PRO_CPU's + * g_ticks_per_us_pro (0x3FFE01E0) as the PRO_CPU set it */ +static void test_app_cpu_rom_data(void) +{ + static const unsigned char spin[3] = { 0x06, 0xff, 0xff }; /* j . */ + t32 t; + u32 pro = 0, app = 0; + if (!open32(&t, spin, sizeof spin, IRAM)) + return; + CHECK_EQ(esk_bus_debug_write(&t.m.bus, 0x3FFE01E0u, 4, 80u), 0); + CHECK_EQ(esk_bus_debug_write(&t.m.bus, 0x3FFE40F0u, 4, 0xdeadbeefu), 0); + /* DPORT: the APP_CPU's clock on, out of reset (APPCPU_CTRL_B, _A) */ + CHECK_EQ(esk_bus_write(&t.m.bus, 0x3FF00030u, 4, 1u), 0); + CHECK_EQ(esk_bus_write(&t.m.bus, 0x3FF0002Cu, 4, 0u), 0); + esk_mach_run(&t.m, 0xffffffffu, 20000); + CHECK_EQ(esk_bus_debug_read(&t.m.bus, 0x3FFE01E0u, 4, &pro), 0); + CHECK_EQ(esk_bus_debug_read(&t.m.bus, 0x3FFE40F0u, 4, &app), 0); + CHECK_EQ(pro, 80u); + CHECK_EQ(app, 13u); /* the ROM's initial value, unpacked by the APP_CPU */ + close32(&t); +} + +TEST_MAIN_BEGIN +RUN(test_alu); +RUN(test_shifts); +RUN(test_muldiv); +RUN(test_loops); +RUN(test_branches); +RUN(test_memory); +RUN(test_windows); +RUN(test_syscall); +RUN(test_timer); +RUN(test_float); +RUN(test_mac16); +RUN(test_app_cpu_rom_data); +TEST_MAIN_END diff --git a/tests/vendor/esp-idf/examples/esp32_hello_world/project.json b/tests/vendor/esp-idf/examples/esp32_hello_world/project.json new file mode 100644 index 000000000..b223b1942 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_hello_world/project.json @@ -0,0 +1,61 @@ +{ + "schema_version": 1, + "kind": "project", + "name": "esp-idf-esp32_hello_world", + "description": "An ESP32-DevKitC V4 running ESP-IDF's hello_world, built unmodified by ESP-IDF's tools for the esp32 target (tests/vendor/esp-idf/run.py puts its 4 MB flash image beside this file as flash.bin), plugged into USB. The console is UART0 through the CP2102N.", + "policy": "electrical", + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc", + "layout": { + "x": 360, + "y": 120 + }, + "firmware": { + "image": "flash.bin", + "format": "bin" + } + } + ], + "components": [ + { + "id": "USB", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5 + }, + "layout": { + "x": 120, + "y": 160 + } + }, + { + "id": "GND1", + "component": "emblink/gnd", + "layout": { + "x": 120, + "y": 320 + } + } + ], + "nets": { + "GND": [ + "USB.-", + "GND1.GND", + "B1.GND1" + ], + "USB5V": [ + "USB.+", + "B1.VBUS" + ] + }, + "probes": [ + { + "id": "console", + "target": "serial:B1", + "kind": "serial" + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/examples/esp32_hello_world/scenario.json b/tests/vendor/esp-idf/examples/esp32_hello_world/scenario.json new file mode 100644 index 000000000..4ae1bd0b2 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_hello_world/scenario.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "kind": "scenario", + "name": "esp32_hello_world", + "until": "11600ms", + "description": "hello_world on the ESP32: Espressif's ROM (ECO3, its banner and boot mode), ESP-IDF's second-stage bootloader (chip revision v3.1 from the eFuses, the image's SHA-256 on the SHA accelerator), the application on both cores at 160 MHz, its chip report, a countdown of eleven one-second FreeRTOS delays, and esp_restart back to the ROM with a software CPU reset of the PRO_CPU.", + "inputs": [], + "assertions": [ + { + "id": "rom", + "at": "100ms", + "probe": "console", + "expect": { + "contains": "rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)" + } + }, + { + "id": "banner", + "at": "100ms", + "probe": "console", + "expect": { + "contains": "ets Jul 29 2019 12:21:46" + } + }, + { + "id": "revision", + "at": "300ms", + "probe": "console", + "expect": { + "contains": "boot: chip revision: v3.1" + } + }, + { + "id": "app", + "at": "500ms", + "probe": "console", + "expect": { + "contains": "cpu_start: Multicore app" + } + }, + { + "id": "freq", + "at": "500ms", + "probe": "console", + "expect": { + "contains": "cpu freq: 160000000 Hz" + } + }, + { + "id": "hello", + "at": "1s", + "probe": "console", + "expect": { + "contains": "Hello world!" + } + }, + { + "id": "chip", + "at": "1s", + "probe": "console", + "expect": { + "contains": "This is esp32 chip with 2 CPU core(s), WiFi/BTBLE, silicon revision v3.1" + } + }, + { + "id": "not-yet", + "at": "11200ms", + "probe": "console", + "expect": { + "not_contains": "Restarting now." + } + }, + { + "id": "restart", + "at": "11600ms", + "probe": "console", + "expect": { + "contains": "rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)" + } + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/run.py b/tests/vendor/esp-idf/run.py index 46f36f9a9..843c2291f 100644 --- a/tests/vendor/esp-idf/run.py +++ b/tests/vendor/esp-idf/run.py @@ -3,11 +3,12 @@ # Copyright 2026 Junior Deogracias """ESP-IDF's own examples for the ESP32-C3, built unmodified by ESP-IDF's build system and run on EmbSIM's ESP32-C3-DevKitM-1 (the RTOS boards plan, -part 2, step 7; 01 §8.2). +part 2, step 7; 01 §8.2), and for the ESP32 on the ESP32-DevKitC V4 (the +examples TARGETS names, built for that target). ESP-IDF is not vendored. --idf names an ESP-IDF checkout (v5.4) whose tools are installed, or IDF_PATH does; the riscv32-esp-elf toolchain must be on -PATH and ESP-IDF's Python environment the one running `idf.py` (its +PATH (xtensa-esp32-elf for the ESP32's) and ESP-IDF's Python environment the one running `idf.py` (its export.sh sets both up). An example's managed components (blink's led_strip) come from Espressif's component registry, as `idf.py` fetches them. @@ -70,7 +71,14 @@ "wifi_softap": "examples/wifi/getting_started/softAP", "wifi_station": "examples/wifi/getting_started/station", "wifi_station_open": "examples/wifi/getting_started/station", + "esp32_hello_world": "examples/get-started/hello_world", } +# the target an example is built for, when not the ESP32-C3 (its board in +# examples//project.json), and the toolchain that target needs +TARGETS = { + "esp32_hello_world": "esp32", +} +TOOLCHAINS = {"esp32c3": "riscv32-esp-elf-gcc", "esp32": "xtensa-esp32-elf-gcc"} # examples built only for another's project (with examples//'s # sdkconfig.defaults), not run on their own VARIANTS = { @@ -100,7 +108,7 @@ def build(idf: Path, name: str, out: Path) -> Path: cmd = [sys.executable, idf / "tools" / "idf.py", "-C", proj, "-B", proj / "build"] if defaults: cmd += ["-D", "SDKCONFIG_DEFAULTS=" + ";".join(str(d) for d in defaults)] - sh(cmd + ["set-target", "esp32c3"], env=env) + sh(cmd + ["set-target", TARGETS.get(name, "esp32c3")], env=env) sh(cmd + ["build"], env=env) args = json.loads((proj / "build" / "flasher_args.json").read_text(encoding="utf-8")) image = bytearray(b"\xff" * FLASH_SIZE) @@ -120,11 +128,16 @@ def main() -> int: ap.add_argument("--keep", help="build here, and keep it, instead of a temporary directory") ap.add_argument("examples", nargs="*") args = ap.parse_args() - if not args.idf or not shutil.which("riscv32-esp-elf-gcc"): - print("skip: ESP-IDF (--idf or IDF_PATH) and its riscv32-esp-elf toolchain are needed") + if not args.idf: + print("skip: ESP-IDF (--idf or IDF_PATH) and its toolchains are needed") return 0 idf = Path(args.idf).resolve() names = args.examples or sorted(EXAMPLES) + missing = sorted({TOOLCHAINS[TARGETS.get(n, "esp32c3")] for n in names} - {t for t in TOOLCHAINS.values() + if shutil.which(t)}) + if missing: + print(f"skip: the toolchain(s) {', '.join(missing)} are needed") + return 0 unknown = [n for n in names if n not in EXAMPLES] if unknown: raise SystemExit(f"unknown example(s): {', '.join(unknown)}") From 32d72597ad8cab2cbed668f55f6518decac72846 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Sun, 8 Nov 2026 03:39:27 +0000 Subject: [PATCH 44/57] ESP-IDF vendor suite: blink on the ESP32-DevKitC V4 The GPIO LED on GPIO2 (sdkconfig.defaults), an LED and 330 ohm on IO2: gpio_set_level through the matrix every second, 6/6. --- .../esp-idf/examples/esp32_blink/project.json | 102 ++++++++++++++++++ .../examples/esp32_blink/scenario.json | 58 ++++++++++ .../examples/esp32_blink/sdkconfig.defaults | 2 + tests/vendor/esp-idf/run.py | 2 + 4 files changed, 164 insertions(+) create mode 100644 tests/vendor/esp-idf/examples/esp32_blink/project.json create mode 100644 tests/vendor/esp-idf/examples/esp32_blink/scenario.json create mode 100644 tests/vendor/esp-idf/examples/esp32_blink/sdkconfig.defaults diff --git a/tests/vendor/esp-idf/examples/esp32_blink/project.json b/tests/vendor/esp-idf/examples/esp32_blink/project.json new file mode 100644 index 000000000..0ba8734e5 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_blink/project.json @@ -0,0 +1,102 @@ +{ + "schema_version": 1, + "kind": "project", + "name": "esp-idf-esp32_blink", + "description": "ESP-IDF's blink on the ESP32-DevKitC V4 with an LED on IO2 through 330 ohm (the DevKitC has no LED of its own; sdkconfig.defaults chooses the GPIO LED on GPIO2).", + "policy": "electrical", + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc", + "layout": { + "x": 360, + "y": 120 + }, + "firmware": { + "image": "flash.bin", + "format": "bin" + } + } + ], + "components": [ + { + "id": "USB", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5 + }, + "layout": { + "x": 120, + "y": 160 + } + }, + { + "id": "GND1", + "component": "emblink/gnd", + "layout": { + "x": 120, + "y": 320 + } + }, + { + "id": "R1", + "component": "emblink/resistor", + "params": { + "resistance": "330" + }, + "layout": { + "x": 600, + "y": 160 + } + }, + { + "id": "LED1", + "component": "emblink/led", + "params": { + "color": "red" + }, + "layout": { + "x": 680, + "y": 160 + } + } + ], + "nets": { + "GND": [ + "USB.-", + "GND1.GND", + "B1.GND1", + "LED1.K" + ], + "USB5V": [ + "USB.+", + "B1.VBUS" + ], + "IO2": [ + "B1.IO2", + "R1.1" + ], + "LA": [ + "R1.2", + "LED1.A" + ] + }, + "probes": [ + { + "id": "console", + "target": "serial:B1", + "kind": "serial" + }, + { + "id": "io2", + "target": "net:IO2", + "kind": "digital" + }, + { + "id": "led", + "target": "output:LED1.luminance", + "kind": "value" + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/examples/esp32_blink/scenario.json b/tests/vendor/esp-idf/examples/esp32_blink/scenario.json new file mode 100644 index 000000000..636b319a9 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_blink/scenario.json @@ -0,0 +1,58 @@ +{ + "schema_version": 1, + "kind": "scenario", + "name": "esp32_blink", + "until": "3900ms", + "description": "blink on the ESP32, the GPIO LED on GPIO2 (sdkconfig.defaults): gpio_set_level through the GPIO matrix every second, the console saying each; the LED off first, then on, off, on.", + "inputs": [], + "assertions": [ + { + "id": "gpio", + "at": "1s", + "probe": "console", + "expect": { + "contains": "Example configured to blink GPIO LED!" + } + }, + { + "id": "on", + "at": "1800ms", + "probe": "io2", + "expect": { + "equals": 1 + } + }, + { + "id": "lit", + "at": "1800ms", + "probe": "led", + "expect": { + "min": 0.1 + } + }, + { + "id": "off", + "at": "2800ms", + "probe": "io2", + "expect": { + "equals": 0 + } + }, + { + "id": "on-again", + "at": "3800ms", + "probe": "io2", + "expect": { + "equals": 1 + } + }, + { + "id": "said", + "at": "3800ms", + "probe": "console", + "expect": { + "contains": "Turning the LED ON!" + } + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/examples/esp32_blink/sdkconfig.defaults b/tests/vendor/esp-idf/examples/esp32_blink/sdkconfig.defaults new file mode 100644 index 000000000..fdea10c5d --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_blink/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_BLINK_LED_GPIO=y +CONFIG_BLINK_GPIO=2 diff --git a/tests/vendor/esp-idf/run.py b/tests/vendor/esp-idf/run.py index 843c2291f..fe8b0bb8f 100644 --- a/tests/vendor/esp-idf/run.py +++ b/tests/vendor/esp-idf/run.py @@ -72,11 +72,13 @@ "wifi_station": "examples/wifi/getting_started/station", "wifi_station_open": "examples/wifi/getting_started/station", "esp32_hello_world": "examples/get-started/hello_world", + "esp32_blink": "examples/get-started/blink", } # the target an example is built for, when not the ESP32-C3 (its board in # examples//project.json), and the toolchain that target needs TARGETS = { "esp32_hello_world": "esp32", + "esp32_blink": "esp32", } TOOLCHAINS = {"esp32c3": "riscv32-esp-elf-gcc", "esp32": "xtensa-esp32-elf-gcc"} # examples built only for another's project (with examples//'s From 055a9f01ea26fbbacada242392267dbe24e07555 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Sun, 8 Nov 2026 03:40:01 +0000 Subject: [PATCH 45/57] CI: the ESP-IDF job installs the ESP32's toolchain too run.py skips an example whose toolchain is missing rather than every example, and CI installs xtensa-esp32-elf with riscv32-esp-elf so the esp32_* examples run on the ESP32-DevKitC. --- .github/workflows/ci.yml | 9 +++++---- tests/vendor/esp-idf/run.py | 7 +++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 060c4e594..673a04a5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -160,8 +160,9 @@ jobs: python3 tests/vendor/freedom-metal/run.py --sdk "$RUNNER_TEMP/freedom-e-sdk" --metal "$RUNNER_TEMP/freedom-metal" --embsim build/${{ matrix.preset }}/bin/embsim esp-idf: - # its own job: ESP-IDF and its RISC-V toolchain take a while to fetch, - # and each example is built with idf.py as a user builds it + # its own job: ESP-IDF and its RISC-V and Xtensa toolchains take a while + # to fetch, and each example is built with idf.py as a user builds it + # (the ESP32-C3-DevKitM-1's, and the ESP32-DevKitC's esp32_* ones) name: ESP-IDF's examples on the ESP32-C3-DevKitM-1 runs-on: ubuntu-24.04 steps: @@ -174,7 +175,7 @@ jobs: run: | cmake --preset ci-gcc cmake --build --preset ci-gcc - - name: ESP-IDF v5.4 and its tools for the ESP32-C3 + - name: ESP-IDF v5.4 and its tools for the ESP32-C3 and the ESP32 run: | # an origin remote: ESP-IDF's submodules are relative to it (../../espressif/...) git init -q "$RUNNER_TEMP/esp-idf" @@ -182,7 +183,7 @@ jobs: git -C "$RUNNER_TEMP/esp-idf" fetch -q --depth 1 origin 67c1de1eebe095d554d281952fde63c16ee2dca0 # v5.4 git -C "$RUNNER_TEMP/esp-idf" checkout -q FETCH_HEAD git -C "$RUNNER_TEMP/esp-idf" submodule update -q --init --depth 1 --recursive - "$RUNNER_TEMP/esp-idf/install.sh" esp32c3 + "$RUNNER_TEMP/esp-idf/install.sh" esp32c3,esp32 - name: Run the examples run: | . "$RUNNER_TEMP/esp-idf/export.sh" >/dev/null diff --git a/tests/vendor/esp-idf/run.py b/tests/vendor/esp-idf/run.py index fe8b0bb8f..5e552a009 100644 --- a/tests/vendor/esp-idf/run.py +++ b/tests/vendor/esp-idf/run.py @@ -135,10 +135,9 @@ def main() -> int: return 0 idf = Path(args.idf).resolve() names = args.examples or sorted(EXAMPLES) - missing = sorted({TOOLCHAINS[TARGETS.get(n, "esp32c3")] for n in names} - {t for t in TOOLCHAINS.values() - if shutil.which(t)}) - if missing: - print(f"skip: the toolchain(s) {', '.join(missing)} are needed") + names = [n for n in names if n not in EXAMPLES or shutil.which(TOOLCHAINS[TARGETS.get(n, "esp32c3")]) + or print(f"skip: {n} needs {TOOLCHAINS[TARGETS.get(n, 'esp32c3')]}")] + if not names: return 0 unknown = [n for n in names if n not in EXAMPLES] if unknown: From 3749b91925c2f2298c8fd452e54e90b9ca44296f Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Sun, 8 Nov 2026 03:43:12 +0000 Subject: [PATCH 46/57] ESP32: LEDC, with ESP-IDF's ledc_basic Eight high-speed and eight low-speed channels on four timers each (core/mcu/esp32/soc/ledc.c, the ESP32-C3's model on the ESP32's registers): REF_TICK, APB_CLK or SLOW_CLK over the 10.8 divider, up to 20 bits of duty, high-speed settings at the next overflow and low-speed ones after PARA_UP, fading, the overflow and fade interrupts, the matrix's ledc_hs/ls_sig_out. ledc_basic's 4 kHz on GPIO5: 2/2. --- CHANGELOG.md | 6 +- catalog/mcus/espressif/esp32/mcu.json | 10 +- core/mcu/esp32/soc/esp32.h | 3 + core/mcu/esp32/soc/ledc.c | 500 ++++++++++++++++++ core/mcu/models.c | 1 + docs/STATUS.md | 5 +- docs/coverage.md | 8 +- .../examples/esp32_ledc_basic/project.json | 66 +++ .../examples/esp32_ledc_basic/scenario.json | 33 ++ tests/vendor/esp-idf/run.py | 2 + 10 files changed, 622 insertions(+), 12 deletions(-) create mode 100644 core/mcu/esp32/soc/ledc.c create mode 100644 tests/vendor/esp-idf/examples/esp32_ledc_basic/project.json create mode 100644 tests/vendor/esp-idf/examples/esp32_ledc_basic/scenario.json diff --git a/CHANGELOG.md b/CHANGELOG.md index f7b911d9e..bd9526d0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,13 +19,13 @@ versions follow semantic versioning. There is no release yet. copy-table entry; the image equals QEMU's esp32-v3-rom.bin), the register file from esp32.svd, and models for DPORT (APP_CPU, caches and flash MMU, interrupt matrix), RTC_CNTL, eFuse, APB_CTRL clocks, GPIO/IO_MUX with - strapping, UART0-2, TIMG0/1, SPI0/1 with the GT25Q32A flash, SHA, the + strapping, UART0-2, TIMG0/1, SPI0/1 with the GT25Q32A flash, SHA, LEDC, the internal analog I2C, RNG and the AHB view. - Added the `emblink/esp32_devkitc` board (ESP32-DevKitC V4, WROOM-32E) with its power path, LED, Boot and EN buttons, headers and fixtures. - Espressif's ROM, ESP-IDF v5.4's bootloader and hello_world run unmodified - through `esp_restart` (`tests/vendor/esp-idf`, example `esp32_hello_world`, - 9/9); `test_lx6` checks the core with programs from Espressif's assembler. + through `esp_restart` (`tests/vendor/esp-idf`: `esp32_hello_world` 9/9, + `esp32_blink` 6/6, `esp32_ledc_basic` 2/2); `test_lx6` checks the core with programs from Espressif's assembler. ### Original ESP32 digital per-pin hold diff --git a/catalog/mcus/espressif/esp32/mcu.json b/catalog/mcus/espressif/esp32/mcu.json index 6bedac0a6..abcc6854c 100644 --- a/catalog/mcus/espressif/esp32/mcu.json +++ b/catalog/mcus/espressif/esp32/mcu.json @@ -191,6 +191,10 @@ { "match": "SHA", "model": "esp32-sha" + }, + { + "match": "LEDC", + "model": "esp32-ledc" } ], "io": { @@ -221,18 +225,18 @@ }, "fidelity": { "functional": { - "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), the internal analog I2C master, the random number generator and the peripherals' AHB view.", + "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), LEDC (eight high-speed and eight low-speed PWM channels on eight timers, fading, on the matrix's ledc_hs/ls_sig_out), the internal analog I2C master, the random number generator and the peripherals' AHB view.", "omissions": [ "Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written)", "the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept", "PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts", - "the peripherals without a model yet: I2C0/1, I2S0/1, SPI2/3 (HSPI, VSPI), RMT, LEDC, MCPWM, PCNT, TWAI, SDMMC, EMAC, the SAR ADCs, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption", + "the peripherals without a model yet: I2C0/1, I2S0/1, SPI2/3 (HSPI, VSPI), RMT, MCPWM, PCNT, TWAI, SDMMC, EMAC, the SAR ADCs, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption", "sleep modes (light and deep sleep, the power domains), brown-out", "the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection" ], "validation": [ "tests/unit/test_lx6.c: programs assembled by Espressif's assembler (integer, shifts, multiply and divide, loops, branches, memory with S32C1I, windowed recursion through the ROM's own window vectors, SYSCALL, the CCOMPARE timer interrupt, FPU, MAC16), and the APP_CPU's ROM data kept apart", - "tests/vendor/esp-idf: ESP-IDF v5.4's hello_world built unmodified for the esp32 target on the ESP32-DevKitC V4, from the ROM's banner to esp_restart", + "tests/vendor/esp-idf: ESP-IDF v5.4's hello_world, blink and ledc_basic built unmodified for the esp32 target on the ESP32-DevKitC V4, from the ROM's banner to esp_restart", "the ROM image equals Espressif QEMU's esp32-v3-rom.bin byte for byte, and the APP_CPU's view of it QEMU's esp32-v3-rom-app.bin" ] }, diff --git a/core/mcu/esp32/soc/esp32.h b/core/mcu/esp32/soc/esp32.h index 65cb9db5e..16b4c970c 100644 --- a/core/mcu/esp32/soc/esp32.h +++ b/core/mcu/esp32/soc/esp32.h @@ -16,6 +16,7 @@ * spi.c SPI0 and SPI1 in front of the flash, and the flash itself * apbctrl.c APB_CTRL: the CPU's and APB's clock dividers * sha.c the SHA accelerator + * ledc.c the LED PWM controller * misc.c the random number generator, the AHB view of the * peripherals */ #ifndef ESK_MCU_ESP32_SOC_H @@ -49,6 +50,8 @@ extern const struct esk_dev_ops esk_esp32_spi_ops; void *esk_esp32_spi_create(struct esk_mach *s, struct esk_rf_periph *p); extern const struct esk_dev_ops esk_esp32_apbctrl_ops; void *esk_esp32_apbctrl_create(struct esk_mach *s, struct esk_rf_periph *p); +extern const struct esk_dev_ops esk_esp32_ledc_ops; +void *esk_esp32_ledc_create(struct esk_mach *s, struct esk_rf_periph *p); extern const struct esk_dev_ops esk_esp32_sha_ops; void *esk_esp32_sha_create(struct esk_mach *s, struct esk_rf_periph *p); /* the RNG and the AHB view of the peripherals (misc.c): 0, or -1 */ diff --git a/core/mcu/esp32/soc/ledc.c b/core/mcu/esp32/soc/ledc.c new file mode 100644 index 000000000..576e4c938 --- /dev/null +++ b/core/mcu/esp32/soc/ledc.c @@ -0,0 +1,500 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* ledc.c -- the ESP32's LED PWM controller (LEDC, TRM v5 chapter 14): + * eight high-speed and eight low-speed channels on four timers each, + * ledc_hs_sig_out0-7 and ledc_ls_sig_out0-7 (signals 71-86 of the GPIO + * matrix). The ESP32-C3's model (mcu/esp32c3/ledc.c) on the ESP32's + * registers. + * + * - A timer counts its clock -- REF_TICK (1 MHz) with TICK_SEL 0; with + * TICK_SEL 1 APB_CLK for the high-speed timers, SLOW_CLK (APB_CLK with + * CONF's APB_CLK_SEL, else the 8 MHz RC_FAST_CLK) for the low-speed ones + * -- divided by DIV_NUM / 256 (10 integer and 8 fractional bits), from + * 0 to 2^DUTY_RES - 1 (up to 20 bits), then overflows; RST holds it at + * 0, PAUSE stops it. A high-speed timer's new DIV_NUM and DUTY_RES + * take effect at its next overflow, a low-speed one's at the overflow + * after PARA_UP. + * - A channel (TIMER_SEL's timer of its group) goes high at its HPOINT + * count and low at HPOINT + DUTY[24:4]; DUTY[3:0] of every 16 periods + * last one count longer; with SIG_OUT_EN 0 it outputs IDLE_LV. Its + * settings take effect at the timer's overflow after DUTY_START (a + * high-speed channel) or the PARA_UP that follows it (a low-speed one). + * - Fading: from DUTY, every DUTY_CYCLE overflows the duty steps + * DUTY_SCALE up (DUTY_INC) or down, DUTY_NUM times, then + * DUTY_CHNG_END. DUTY_R is the duty in use. + * - INT_ST is INT_RAW and INT_ENA (the timers' overflows, bits 0-7; the + * channels' fade ends, 8-23), raising matrix source 43; INT_CLR clears. + * Where it differs inside: a fractional divider's counts are all of the + * average length; the dithered periods are spread by an accumulator. + * Not yet: light sleep, the clock gate's effect on a running timer. */ +#include +#include + +#include "mcu/esp32/soc/esp32.h" + +#define NCH 16 /* HSCH0-7, LSCH0-7 */ +#define NTM 8 /* HSTIMER0-3, LSTIMER0-3 */ +#define SIG_OUT0 71 /* ledc_hs_sig_out0; ledc_ls_sig_out0 is 79 */ +#define RC_FAST_HZ 8000000u +#define REF_TICK_HZ 1000000u +#define FS_PER_S 1000000000000000ull + +#define T_PAUSE (1u << 23) +#define T_RST (1u << 24) +#define T_TICK_SEL (1u << 25) +#define T_PARA_UP (1u << 26) +#define C_SIG_OUT_EN (1u << 2) +#define C_IDLE_LV (1u << 3) +#define C_PARA_UP (1u << 4) +#define F_INC (1u << 30) +#define F_START (1u << 31) + +struct tmr { + struct esk_rf_reg *conf, *value; + u32 res, div; /* in use: DUTY_RES, CLK_DIV */ + int update; /* PARA_UP: the new ones at the next overflow */ + u32 cnt; /* the count */ + u64 pos; /* fs into the period */ +}; + +struct chan { + struct esk_rf_reg *conf0, *hpoint, *duty, *conf1, *duty_r; + int update; /* PARA_UP: the new settings at the next overflow */ + u32 sel, hp, en, idle; /* in use */ + u32 duty_cur; /* the duty (19 bits, 4 of them fractions) */ + u32 lp; /* this period's low point */ + u32 dither; /* the fractions' accumulator */ + int fading; + u32 cyc, steps, ovf; + int out; +}; + +struct e32_ledc { + struct esk_mach *s; + struct esk_rf_periph *p; + struct esk_rf_reg *conf, *raw, *st, *ena; + struct tmr t[NTM]; + struct chan c[NCH]; + int clocked, level, source; +}; + +static u32 reg(const struct esk_rf_reg *r) { return (u32)r->value; } + +static void update(struct e32_ledc *k) +{ + u32 st = reg(k->raw) & reg(k->ena) & 0xffffffu; + int level = st != 0; + esk_rf_hw(k->st, st); + if (level != k->level) { + k->level = level; + esk_esp32_source(k->s, k->source, level); + } +} + +static void raise(struct e32_ledc *k, u32 bits) +{ + esk_rf_hw(k->raw, reg(k->raw) | bits); + update(k); +} + +/* a count's length in fs, or 0 while the timer does not count */ +static double tick_fs(const struct e32_ledc *k, const struct tmr *t) +{ + u32 hz, c = reg(t->conf), x = (u32)(t - k->t); + if ((c & (T_RST | T_PAUSE)) || t->div < 256u) + return 0; + if (!(c & T_TICK_SEL)) + hz = REF_TICK_HZ; + else if (x < 4u || (reg(k->conf) & 1u)) + hz = esk_esp32_apb_hz(k->s); /* high speed: APB_CLK; low speed: SLOW_CLK */ + else + hz = RC_FAST_HZ; + if (!hz) + return 0; + return (double)t->div / 256.0 * (double)FS_PER_S / (double)hz; +} + +static u32 top(const struct tmr *t) { return (1u << (t->res > 20u ? 20u : t->res)) - 1u; } + +static u64 fs_per_cycle(const struct e32_ledc *k) +{ + u32 hz = esk_mach_core_hz(k->s); + return hz ? FS_PER_S / hz : 0; +} + +static void drive(struct e32_ledc *k, int n, int out) +{ + struct chan *c = &k->c[n]; + if (out != c->out) { + c->out = out; + esk_esp32_signal(k->s, SIG_OUT0 + n, out, 1); + } +} + +/* the channel's settings as written, in use from now */ +static void chan_apply(struct e32_ledc *k, int n) +{ + struct chan *c = &k->c[n]; + u32 c0 = reg(c->conf0); + c->sel = (c0 & 3u) + (n >= 8 ? 4u : 0u); /* its group's timer */ + c->hp = reg(c->hpoint) & 0xfffffu; + c->en = (c0 & C_SIG_OUT_EN) != 0; + c->idle = (c0 & C_IDLE_LV) != 0; + c->update = 0; + if (!c->en) + drive(k, n, (int)c->idle); +} + +/* the low point of the period starting: HPOINT + the duty, a count longer + * in DUTY[3:0] periods of 16 */ +static void period_start(struct e32_ledc *k, int n) +{ + struct chan *c = &k->c[n]; + u32 frac = c->duty_cur & 0xfu, d = c->duty_cur >> 4; + c->dither += frac; + if (c->dither >= 16u) { + c->dither -= 16u; + d++; + } + c->lp = c->hp + d; + esk_rf_hw(c->duty_r, c->duty_cur); +} + +/* the timer overflowed: what waits for it */ +static void overflow(struct e32_ledc *k, int x) +{ + struct tmr *t = &k->t[x]; + int n; + if (t->update) { + t->res = reg(t->conf) & 0x1fu; + t->div = (reg(t->conf) >> 5) & 0x3ffffu; + t->update = 0; + } + raise(k, 1u << x); + for (n = 0; n < NCH; n++) { + struct chan *c = &k->c[n]; + u32 c1; + if (c->sel != (u32)x) + continue; + if (c->update) + chan_apply(k, n); + c1 = reg(c->conf1); + if (c->fading) { + u32 every = (c1 >> 10) & 0x3ffu; + if (++c->cyc >= (every ? every : 1u)) { + u32 num = (c1 >> 20) & 0x3ffu, scale = (c1 & 0x3ffu) << 4; + c->cyc = 0; + if (c->steps < num) { + if (c1 & F_INC) + c->duty_cur = c->duty_cur + scale > 0x1ffffffu ? 0x1ffffffu : c->duty_cur + scale; + else + c->duty_cur = c->duty_cur > scale ? c->duty_cur - scale : 0; + c->steps++; + } + if (c->steps >= num) { + c->fading = 0; + raise(k, 1u << (8 + n)); + } + } + } + if (c->sel == (u32)x) + period_start(k, n); + } +} + +/* the timer entered count `cnt`: the outputs that switch there */ +static void at_count(struct e32_ledc *k, int x) +{ + struct tmr *t = &k->t[x]; + int n; + for (n = 0; n < NCH; n++) { + struct chan *c = &k->c[n]; + if (c->sel != (u32)x || !c->en) + continue; + if (t->cnt == c->hp && c->lp != c->hp) + drive(k, n, 1); + if (t->cnt == c->lp) + drive(k, n, 0); + } +} + +/* the next count after the present one where something happens: an output + * switching or the overflow (top + 1) */ +static u32 next_count(const struct e32_ledc *k, int x) +{ + const struct tmr *t = &k->t[x]; + u32 best = top(t) + 1u; + int n; + for (n = 0; n < NCH; n++) { + const struct chan *c = &k->c[n]; + if (c->sel != (u32)x || !c->en) + continue; + if (c->hp > t->cnt && c->hp < best) + best = c->hp; + if (c->lp > t->cnt && c->lp < best) + best = c->lp; + } + return best; +} + +/* fs from the present to the timer's next event, or 0 when it stands */ +static u64 due(const struct e32_ledc *k, int x) +{ + const struct tmr *t = &k->t[x]; + double tf = tick_fs(k, t); + u64 at; + if (tf <= 0) + return 0; + at = (u64)((double)next_count(k, x) * tf + 0.5); + return at > t->pos ? at - t->pos : 1u; +} + +static void clocking(struct e32_ledc *k) +{ + int x, on = 0; + for (x = 0; x < NTM; x++) + if (tick_fs(k, &k->t[x]) > 0) + on = 1; + esk_rf_clock(k->p, &k->clocked, on); +} + +static void advance(struct e32_ledc *k, u64 budget) +{ + struct esk_mach *s = k->s; + u64 fpc = fs_per_cycle(k); + int x; + if (!fpc) + return; + for (;;) { + u64 d = 0; + int which = -1; + for (x = 0; x < NTM; x++) { + u64 dx = due(k, x); + if (dx && (which < 0 || dx < d)) { + d = dx; + which = x; + } + } + if (which < 0 || d > budget) { + for (x = 0; x < NTM; x++) { + struct tmr *t = &k->t[x]; + double tf = tick_fs(k, t); + if (tf <= 0) + continue; + t->pos += budget; + t->cnt = (u32)((double)t->pos / tf); + if (t->cnt > top(t)) + t->cnt = top(t); + } + break; + } + budget -= d; + s->output_lag = (u32)(budget / fpc); + for (x = 0; x < NTM; x++) { + struct tmr *t = &k->t[x]; + double tf = tick_fs(k, t); + if (tf <= 0) + continue; + if (x == which) { + u32 c = next_count(k, x); + t->pos += d; + if (c > top(t)) { + t->pos = 0; + t->cnt = 0; + overflow(k, x); + } else { + t->cnt = c; + } + at_count(k, x); + } else { + t->pos += d; + t->cnt = (u32)((double)t->pos / tf); + } + } + s->output_lag = 0; + } + for (x = 0; x < NTM; x++) + esk_rf_hw(k->t[x].value, k->t[x].cnt & 0xfffffu); +} + +static void ledc_tick(void *ctx, u32 cycles) +{ + struct e32_ledc *k = (struct e32_ledc *)ctx; + advance(k, (u64)cycles * fs_per_cycle(k)); +} + +static int ledc_next(void *ctx, u32 *cycles) +{ + struct e32_ledc *k = (struct e32_ledc *)ctx; + u64 fpc = fs_per_cycle(k), best = 0; + int x, any = 0; + if (!fpc) + return 0; + for (x = 0; x < NTM; x++) { + u64 d = due(k, x), c; + if (!d) + continue; + c = (d + fpc - 1u) / fpc; + if (!any || c < best) + best = c; + any = 1; + } + if (!any) + return 0; + *cycles = best == 0 ? 1u : best > 0xffffffffu ? 0xffffffffu : (u32)best; + return 1; +} + +static u32 ledc_read(void *ctx, u32 off, int n) +{ + struct e32_ledc *k = (struct e32_ledc *)ctx; + return esk_rf_read(k->p, off, n); +} + +/* the timer starts counting (out of RST): its generators' first period */ +static void timer_starts(struct e32_ledc *k, int x) +{ + int n; + for (n = 0; n < NCH; n++) + if (k->c[n].sel == (u32)x) + period_start(k, n); + at_count(k, x); +} + +static void ledc_write(void *ctx, u32 off, int n, u32 v) +{ + struct e32_ledc *k = (struct e32_ledc *)ctx; + u32 w = off & ~3u, held[NTM]; + int i; + for (i = 0; i < NTM; i++) + held[i] = reg(k->t[i].conf) & T_RST; + if (w == 0x18Cu) { /* INT_CLR */ + esk_rf_hw(k->raw, reg(k->raw) & ~v); + update(k); + return; + } + if (w == 0x184u) + return; /* INT_ST */ + esk_rf_write(k->p, off, n, v); + if (w == 0x180u || w == 0x188u) + update(k); + for (i = 0; i < NTM; i++) { + struct tmr *t = &k->t[i]; + if (w != esk_rf_off(t->conf)) + continue; + if (reg(t->conf) & T_RST) { + t->cnt = 0; + t->pos = 0; + } + if (i < 4 || (v & T_PARA_UP)) + t->update = 1; /* a high-speed timer's settings: at its next overflow */ + if (!t->div && !t->res) /* never set: the first settings at once */ + t->update = 1; + if (t->update && (reg(t->conf) & T_RST)) { /* held: nothing to wait for */ + t->res = reg(t->conf) & 0x1fu; + t->div = (reg(t->conf) >> 5) & 0x3ffffu; + t->update = 0; + } + if (held[i] && !(reg(t->conf) & T_RST)) + timer_starts(k, i); + } + for (i = 0; i < NCH; i++) { + struct chan *c = &k->c[i]; + /* a high-speed channel takes DUTY_START (CONF1), a low-speed one + * its PARA_UP (CONF0) after it */ + if (!(i < 8 ? w == esk_rf_off(c->conf1) && (v & F_START) : w == esk_rf_off(c->conf0) && (v & C_PARA_UP))) + continue; + { + u32 c1 = reg(c->conf1); + c->update = 1; + c->duty_cur = reg(c->duty) & 0x1ffffffu; /* the fade's start, at once */ + c->fading = (c1 & F_START) != 0; + c->cyc = 0; + c->steps = 0; + esk_rf_hw(c->duty_r, c->duty_cur); + if (tick_fs(k, &k->t[c->sel]) <= 0) { /* no overflow coming: now */ + chan_apply(k, i); + period_start(k, i); + } + } + } + clocking(k); +} + +static void ledc_reset(void *ctx) +{ + struct e32_ledc *k = (struct e32_ledc *)ctx; + int i; + for (i = 0; i < NTM; i++) { + struct tmr *t = &k->t[i]; + t->res = reg(t->conf) & 0x1fu; + t->div = (reg(t->conf) >> 5) & 0x3ffffu; + t->update = 0; + t->cnt = 0; + t->pos = 0; + } + for (i = 0; i < NCH; i++) { + struct chan *c = &k->c[i]; + c->update = 0; + c->duty_cur = 0; + c->lp = 0; + c->dither = 0; + c->fading = 0; + c->cyc = c->steps = c->ovf = 0; + c->out = -1; + chan_apply(k, i); + drive(k, i, (int)c->idle); + } + k->level = 0; + esk_esp32_source(k->s, k->source, 0); + k->clocked = 0; + update(k); + clocking(k); +} + +const struct esk_dev_ops esk_esp32_ledc_ops = { + "esp32-ledc", ledc_read, ledc_write, ledc_reset, ledc_tick, ledc_next, +}; + +void *esk_esp32_ledc_create(struct esk_mach *s, struct esk_rf_periph *p) +{ + struct e32_ledc *k = (struct e32_ledc *)esk_mach_alloc(s, sizeof *k); + char name[24]; + int i; + if (!k) + return 0; + memset(k, 0, sizeof *k); + k->s = s; + k->p = p; + k->conf = esk_rf_reg(p, "CONF"); + k->raw = esk_rf_reg(p, "INT_RAW"); + k->st = esk_rf_reg(p, "INT_ST"); + k->ena = esk_rf_reg(p, "INT_ENA"); + if (!k->conf || !k->raw || !k->st || !k->ena) + return 0; + k->source = esk_rf_irq(p, "LEDC"); + for (i = 0; i < NTM; i++) { + snprintf(name, sizeof name, "%sTIMER%d_CONF", i < 4 ? "HS" : "LS", i % 4); + k->t[i].conf = esk_rf_reg(p, name); + snprintf(name, sizeof name, "%sTIMER%d_VALUE", i < 4 ? "HS" : "LS", i % 4); + k->t[i].value = esk_rf_reg(p, name); + if (!k->t[i].conf || !k->t[i].value) + return 0; + } + for (i = 0; i < NCH; i++) { + struct chan *c = &k->c[i]; + snprintf(name, sizeof name, "%sCH%d_CONF0", i < 8 ? "HS" : "LS", i % 8); + c->conf0 = esk_rf_reg(p, name); + snprintf(name, sizeof name, "%sCH%d_HPOINT", i < 8 ? "HS" : "LS", i % 8); + c->hpoint = esk_rf_reg(p, name); + snprintf(name, sizeof name, "%sCH%d_DUTY", i < 8 ? "HS" : "LS", i % 8); + c->duty = esk_rf_reg(p, name); + snprintf(name, sizeof name, "%sCH%d_CONF1", i < 8 ? "HS" : "LS", i % 8); + c->conf1 = esk_rf_reg(p, name); + snprintf(name, sizeof name, "%sCH%d_DUTY_R", i < 8 ? "HS" : "LS", i % 8); + c->duty_r = esk_rf_reg(p, name); + if (!c->conf0 || !c->hpoint || !c->duty || !c->conf1 || !c->duty_r) + return 0; + } + return k; +} diff --git a/core/mcu/models.c b/core/mcu/models.c index 954533580..314801791 100644 --- a/core/mcu/models.c +++ b/core/mcu/models.c @@ -71,6 +71,7 @@ static const struct esk_rf_model_type model_types[] = { { "esp32-spi", &esk_esp32_spi_ops, esk_esp32_spi_create }, { "esp32-timg", &esk_esp32_timg_ops, esk_esp32_timg_create }, { "esp32-sha", &esk_esp32_sha_ops, esk_esp32_sha_create }, + { "esp32-ledc", &esk_esp32_ledc_ops, esk_esp32_ledc_create }, { "esp32-iomux", &esk_esp32_iomux_ops, esk_esp32_iomux_create }, { "esp32c3-intmtx", &esk_esp32c3_intmtx_ops, esk_esp32c3_intmtx_create }, { "esp32c3-uart", &esk_esp32c3_uart_ops, esk_esp32c3_uart_create }, diff --git a/docs/STATUS.md b/docs/STATUS.md index d43415860..c563bbea0 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -14,9 +14,10 @@ Espressif's ROM (ECO3), ESP-IDF v5.4's second-stage bootloader and its hello_world running unmodified on both cores at 160 MHz to `Hello world!` and through `esp_restart` (rst:0xc SW_CPU_RESET). Evidence: `test_lx6` (4,332 checks), the board's three fixtures, and the ESP-IDF vendor -scenario `esp32_hello_world` (9/9). Not yet: Wi-Fi and Bluetooth, the +scenarios `esp32_hello_world` (9/9), `esp32_blink` (6/6) and +`esp32_ledc_basic` (2/2). Not yet: Wi-Fi and Bluetooth, the double-precision accelerator's instructions, the peripherals the -description's fidelity lists (I2C, I2S, SPI2/3, RMT, LEDC, ADC, ...), +description's fidelity lists (I2C, I2S, SPI2/3, RMT, ADC, ...), sleep. The standalone harness below remains as its own qualification fixtures. diff --git a/docs/coverage.md b/docs/coverage.md index 3c4dc6e27..00c64da55 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -8,7 +8,7 @@ evidence for each model is in [STATUS.md](STATUS.md). | MCU | Modelled | Registers only | Missing | Peripherals | |---|---|---|---|---| -| ESP32 | 14 | 0 | 31 | 45 | +| ESP32 | 15 | 0 | 30 | 45 | | ESP32-C3 | 35 | 0 | 0 | 35 | | ATmega328P | 15 | 0 | 0 | 15 | | PIC24FJ1024GB610 | 11 | 4 | 17 | 32 | @@ -23,7 +23,7 @@ evidence for each model is in [STATUS.md](STATUS.md). ## ESP32 (`espressif/esp32`) Boards: ESP32-DevKitC V4. -14 of 45 peripherals modelled, 0 registers only, 31 missing. +15 of 45 peripherals modelled, 0 registers only, 30 missing. | Peripheral | Model | State | |---|---|---| @@ -53,7 +53,7 @@ Boards: ESP32-DevKitC V4. | RMT | — | **missing** | | PCNT | — | **missing** | | SLC | — | **missing** | -| LEDC | — | **missing** | +| LEDC | `esp32-ledc` | modelled | | EFUSE | `esp32-efuse` | modelled | | NRX | — | **missing** | | BB | — | **missing** | @@ -73,7 +73,7 @@ Boards: ESP32-DevKitC V4. | I2S1 | — | **missing** | | UART2 | `esp32-uart` | modelled | -Declared omissions: Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written); the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept; PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts; the peripherals without a model yet: I2C0/1, I2S0/1, SPI2/3 (HSPI, VSPI), RMT, LEDC, MCPWM, PCNT, TWAI, SDMMC, EMAC, the SAR ADCs, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption; sleep modes (light and deep sleep, the power domains), brown-out; the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection. +Declared omissions: Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written); the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept; PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts; the peripherals without a model yet: I2C0/1, I2S0/1, SPI2/3 (HSPI, VSPI), RMT, MCPWM, PCNT, TWAI, SDMMC, EMAC, the SAR ADCs, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption; sleep modes (light and deep sleep, the power domains), brown-out; the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection. ## ESP32-C3 (`espressif/esp32c3`) diff --git a/tests/vendor/esp-idf/examples/esp32_ledc_basic/project.json b/tests/vendor/esp-idf/examples/esp32_ledc_basic/project.json new file mode 100644 index 000000000..dab003ced --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_ledc_basic/project.json @@ -0,0 +1,66 @@ +{ + "schema_version": 1, + "kind": "project", + "name": "esp-idf-esp32_ledc_basic", + "description": "ESP-IDF's ledc_basic on the ESP32-DevKitC V4: the low-speed timer 0 at 4 kHz, 13 bits, channel 0 at half duty on GPIO5.", + "policy": "electrical", + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc", + "layout": { + "x": 360, + "y": 120 + }, + "firmware": { + "image": "flash.bin", + "format": "bin" + } + } + ], + "components": [ + { + "id": "USB", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5 + }, + "layout": { + "x": 120, + "y": 160 + } + }, + { + "id": "GND1", + "component": "emblink/gnd", + "layout": { + "x": 120, + "y": 320 + } + } + ], + "nets": { + "USB5V": [ + "USB.+", + "B1.VBUS" + ], + "GND": [ + "USB.-", + "GND1.GND", + "B1.GND1" + ] + }, + "probes": [ + { + "id": "console", + "target": "serial:B1", + "kind": "serial" + }, + { + "id": "pwm", + "target": "net:B1.GPIO5", + "kind": "digital" + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/examples/esp32_ledc_basic/scenario.json b/tests/vendor/esp-idf/examples/esp32_ledc_basic/scenario.json new file mode 100644 index 000000000..2fffa0c63 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_ledc_basic/scenario.json @@ -0,0 +1,33 @@ +{ + "schema_version": 1, + "kind": "scenario", + "name": "esp32_ledc_basic", + "until": "1200ms", + "description": "ledc_basic on the ESP32: the low-speed timer 0 at 4 kHz with 13 bits from APB_CLK (ESP-IDF's divider, CONF's APB_CLK_SEL), channel 0 at a duty of 4096/8192 on GPIO5 through the GPIO matrix (ledc_ls_sig_out0).", + "inputs": [], + "assertions": [ + { + "id": "frequency", + "between": [ + "1000ms", + "1100ms" + ], + "probe": "pwm", + "expect": { + "frequency_hz": 4000, + "tolerance": 0.01 + } + }, + { + "id": "edges", + "between": [ + "1000ms", + "1100ms" + ], + "probe": "pwm", + "expect": { + "edges": 800 + } + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/run.py b/tests/vendor/esp-idf/run.py index 5e552a009..55b9e73b1 100644 --- a/tests/vendor/esp-idf/run.py +++ b/tests/vendor/esp-idf/run.py @@ -73,12 +73,14 @@ "wifi_station_open": "examples/wifi/getting_started/station", "esp32_hello_world": "examples/get-started/hello_world", "esp32_blink": "examples/get-started/blink", + "esp32_ledc_basic": "examples/peripherals/ledc/ledc_basic", } # the target an example is built for, when not the ESP32-C3 (its board in # examples//project.json), and the toolchain that target needs TARGETS = { "esp32_hello_world": "esp32", "esp32_blink": "esp32", + "esp32_ledc_basic": "esp32", } TOOLCHAINS = {"esp32c3": "riscv32-esp-elf-gcc", "esp32": "xtensa-esp32-elf-gcc"} # examples built only for another's project (with examples//'s From 5fabb5c107f970e8fea87853ecbd88d4f81ab767 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Sun, 8 Nov 2026 03:45:32 +0000 Subject: [PATCH 47/57] ESP32: the supply current from the datasheet's power modes Datasheet v5.3 table 4-2: Modem-sleep of the dual-core chips at 80, 160 and 240 MHz (the range's low end waiting, high end running), 1 uA held; table 5-4's radio currents for when the radios are modelled. --- catalog/mcus/espressif/esp32/mcu.json | 44 +++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/catalog/mcus/espressif/esp32/mcu.json b/catalog/mcus/espressif/esp32/mcu.json index abcc6854c..c86b2d9aa 100644 --- a/catalog/mcus/espressif/esp32/mcu.json +++ b/catalog/mcus/espressif/esp32/mcu.json @@ -220,6 +220,44 @@ "iol_max": 0.028 } }, + "supply": { + "doc": "ESP32 Series Datasheet v5.3, table 4-2 (power consumption by power modes): Modem-sleep of the dual-core chips, 20-31 mA at 80 MHz, 27-44 mA at 160 MHz, 30-68 mA at 240 MHz, taken as the low end with the CPUs waiting and the high end running (the datasheet gives the range by load, not the split); power off with CHIP_PU low 1 uA. The radios' currents of table 5-4 (802.11b 1 Mbps at +19.5 dBm 240 mA, 802.11g 54 Mbps at +16 dBm 190 mA, 802.11n MCS7 at +14 dBm 180 mA, receiving 95-100 mA) for when they are modelled.", + "run_a": [ + [ + 80000000, + 0.031 + ], + [ + 160000000, + 0.044 + ], + [ + 240000000, + 0.068 + ] + ], + "idle_a": [ + [ + 80000000, + 0.02 + ], + [ + 160000000, + 0.027 + ], + [ + 240000000, + 0.03 + ] + ], + "held_a": 1e-06, + "radio": { + "rx_a": 0.1, + "tx_dsss_a": 0.24, + "tx_ofdm_a": 0.19, + "tx_ht_a": 0.18 + } + }, "console": { "default": "UART0" }, @@ -250,10 +288,10 @@ "validation": [] }, "electrical": { - "claims": "GPIO pin-model defaults at 3.3 V from the datasheet's DC characteristics: VIH 0.75 VDD, VIL 0.25 VDD, VOH 0.8 VDD, VOL 0.1 VDD, 45 kOhm pulls; GPIO34-39 inputs only.", + "claims": "GPIO pin-model defaults at 3.3 V from the datasheet's DC characteristics: VIH 0.75 VDD, VIL 0.25 VDD, VOH 0.8 VDD, VOL 0.1 VDD, 45 kOhm pulls; GPIO34-39 inputs only. The supply current (ADR-036, datasheet v5.3's power modes): the cores running and waiting at their clock, and held off.", "omissions": [ - "the supply current (the datasheet's power modes are not yet in the description)", - "the pads' drive strengths' effect" + "the pads' drive strengths' effect", + "each peripheral's own current (the datasheet gives the modes, not the parts)" ], "validation": [] } From 2f3d4df74a61534876f9a567e8da5eb4c222b6ae Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Sun, 8 Nov 2026 04:08:53 +0000 Subject: [PATCH 48/57] ESP32: I2C0 and I2C1, with ESP-IDF's i2c_eeprom The ESP32's I2C controllers from the C3's bit-level model on the ESP32's registers: 16 commands with its op codes (RSTART 0, WRITE 1, READ 2, STOP 3, END 4), the bus timing in APB_CLK cycles as i2c_ll counts it (SCL high + 7, or the filter's share), ACK_ERR, arbitration, the SCL timeout, the FIFOs through DATA and its AHB view, master and slave. The GPIO model gains the signal watch the controller follows the lines with, and DPORT's PERIP_RST_EN resets I2C0, I2C1 and LEDC. ESP-IDF v5.4's i2c_eeprom passes unmodified on the DevKitC with a 24LC256 (esp32_i2c_eeprom 3/3). --- CHANGELOG.md | 8 +- catalog/mcus/espressif/esp32/mcu.json | 9 +- core/mcu/esp32/soc/dport.c | 29 +- core/mcu/esp32/soc/esp32.h | 5 + core/mcu/esp32/soc/gpio.c | 26 +- core/mcu/esp32/soc/i2c.c | 944 ++++++++++++++++++ core/mcu/models.c | 1 + docs/STATUS.md | 7 +- docs/coverage.md | 10 +- .../examples/esp32_i2c_eeprom/project.json | 126 +++ .../examples/esp32_i2c_eeprom/scenario.json | 34 + tests/vendor/esp-idf/run.py | 2 + 12 files changed, 1182 insertions(+), 19 deletions(-) create mode 100644 core/mcu/esp32/soc/i2c.c create mode 100644 tests/vendor/esp-idf/examples/esp32_i2c_eeprom/project.json create mode 100644 tests/vendor/esp-idf/examples/esp32_i2c_eeprom/scenario.json diff --git a/CHANGELOG.md b/CHANGELOG.md index bd9526d0a..b94e50c75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,13 +19,15 @@ versions follow semantic versioning. There is no release yet. copy-table entry; the image equals QEMU's esp32-v3-rom.bin), the register file from esp32.svd, and models for DPORT (APP_CPU, caches and flash MMU, interrupt matrix), RTC_CNTL, eFuse, APB_CTRL clocks, GPIO/IO_MUX with - strapping, UART0-2, TIMG0/1, SPI0/1 with the GT25Q32A flash, SHA, LEDC, the - internal analog I2C, RNG and the AHB view. + strapping, UART0-2, TIMG0/1, SPI0/1 with the GT25Q32A flash, SHA, LEDC, + I2C0/1 (master and slave, 16 commands, APB_CLK timing; DPORT's + PERIP_RST_EN resets them), the internal analog I2C, RNG and the AHB view. - Added the `emblink/esp32_devkitc` board (ESP32-DevKitC V4, WROOM-32E) with its power path, LED, Boot and EN buttons, headers and fixtures. - Espressif's ROM, ESP-IDF v5.4's bootloader and hello_world run unmodified through `esp_restart` (`tests/vendor/esp-idf`: `esp32_hello_world` 9/9, - `esp32_blink` 6/6, `esp32_ledc_basic` 2/2); `test_lx6` checks the core with programs from Espressif's assembler. + `esp32_blink` 6/6, `esp32_ledc_basic` 2/2, `esp32_i2c_eeprom` 3/3 on a + 24LC256); `test_lx6` checks the core with programs from Espressif's assembler. ### Original ESP32 digital per-pin hold diff --git a/catalog/mcus/espressif/esp32/mcu.json b/catalog/mcus/espressif/esp32/mcu.json index c86b2d9aa..1bdc0f9b9 100644 --- a/catalog/mcus/espressif/esp32/mcu.json +++ b/catalog/mcus/espressif/esp32/mcu.json @@ -195,6 +195,10 @@ { "match": "LEDC", "model": "esp32-ledc" + }, + { + "match": "I2C?", + "model": "esp32-i2c" } ], "io": { @@ -263,12 +267,13 @@ }, "fidelity": { "functional": { - "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), LEDC (eight high-speed and eight low-speed PWM channels on eight timers, fading, on the matrix's ledc_hs/ls_sig_out), the internal analog I2C master, the random number generator and the peripherals' AHB view.", + "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), LEDC (eight high-speed and eight low-speed PWM channels on eight timers, fading, on the matrix's ledc_hs/ls_sig_out), I2C0 and I2C1 (master and slave on the open-drain lines through the matrix, the 16 commands with RSTART, WRITE, READ, STOP and END, the bus timing in APB_CLK cycles, ACK checks, arbitration, the SCL timeout, the 32-byte FIFOs through DATA and its AHB view, 7- and 10-bit slave addresses; ESP-IDF's i2c_eeprom runs on a 24LC256), PERIP_RST_EN's resets of I2C0, I2C1 and LEDC, the internal analog I2C master, the random number generator and the peripherals' AHB view.", "omissions": [ "Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written)", "the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept", "PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts", - "the peripherals without a model yet: I2C0/1, I2S0/1, SPI2/3 (HSPI, VSPI), RMT, MCPWM, PCNT, TWAI, SDMMC, EMAC, the SAR ADCs, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption", + "I2C: the non-FIFO mode (NONFIFO_EN, RX_REC_FULL, TX_SEND_EMPTY), SAMPLE_SCL_LEVEL, the input filters (only their share of the SCL high time), 10-bit addressing's R/W check", + "the peripherals without a model yet: I2S0/1, SPI2/3 (HSPI, VSPI), RMT, MCPWM, PCNT, TWAI, SDMMC, EMAC, the SAR ADCs, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption", "sleep modes (light and deep sleep, the power domains), brown-out", "the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection" ], diff --git a/core/mcu/esp32/soc/dport.c b/core/mcu/esp32/soc/dport.c index f72c1c48c..1091cfbd1 100644 --- a/core/mcu/esp32/soc/dport.c +++ b/core/mcu/esp32/soc/dport.c @@ -26,7 +26,9 @@ * pages whose flash the program has written since they were mapped * show it after a flush; * - CPU_PER_CONF's CPUPERIOD_SEL, which with RTC_CNTL's SOC_CLK_SEL and - * APB_CTRL's divider sets the CPU's clock (apbctrl.c). + * APB_CTRL's divider sets the CPU's clock (apbctrl.c); + * - PERIP_RST_EN: setting the bit of I2C0 (7), LEDC (11) or I2C1 (18) + * returns that peripheral to its reset state. * Inside, the caches hold the mapped pages whole rather than 32 KB of * lines, and both CPUs read through the PRO_CPU's table (ESP-IDF keeps * the two the same); a CPU's cache enable and the buses' mask bits @@ -34,7 +36,9 @@ * Not yet: the cache's lock regions, PSRAM (DRAM1, which the module does * not have), the boot remap (PRO_BOOT_REMAP_CTRL), the APB and AHB * access permissions (DPORT_APB_MASK, AHBLITE_MPU_TABLE), the MMU's - * access-error interrupts, the trace memory and the CPU record. */ + * access-error interrupts, the trace memory and the CPU record, the other + * peripherals' PERIP_RST_EN bits and every PERIP_CLK_EN gate (a gated + * peripheral still answers). */ #include #include "mcu/esp32/soc/esp32.h" @@ -56,6 +60,14 @@ #define APP_DCACHE_DBUG0 0x418u #define CACHE_FLUSH_ENA 0x10u #define CACHE_FLUSH_DONE 0x20u +#define PERIP_RST_EN 0xC4u + +/* PERIP_RST_EN's bits of the modelled peripherals */ +static const struct { + const char *name; + int bit; +} resets[] = { { "I2C0", 7 }, { "LEDC", 11 }, { "I2C1", 18 } }; +#define NRESETS (sizeof resets / sizeof resets[0]) #define MMU_BASE 0x3FF10000u #define MMU_WORDS 2048 /* per CPU: 0x2000 bytes */ @@ -74,6 +86,7 @@ struct e32_dport { u32 mmu[2][MMU_WORDS]; u8 stale[128]; u8 *app_rom; /* the ROM as the APP_CPU sees it */ + struct esk_rf_periph *rst[3]; /* resets[]'s peripherals */ }; static struct e32_dport *dport_of(struct esk_mach *m) @@ -287,8 +300,15 @@ static u32 dp_read(void *ctx, u32 off, int n) static void dp_write(void *ctx, u32 off, int n, u32 v) { struct e32_dport *k = (struct e32_dport *)ctx; + u32 was = reg(k, PERIP_RST_EN); + size_t i; esk_rf_write(k->p, off, n, v); - if (off >= APPCPU_CTRL_A && off <= APPCPU_CTRL_C) { + if (off == PERIP_RST_EN) { + u32 now = reg(k, PERIP_RST_EN); + for (i = 0; i < NRESETS; i++) + if (k->rst[i] && (now & ~was & (1u << resets[i].bit))) + esk_rf_periph_reset(k->rst[i]); + } else if (off >= APPCPU_CTRL_A && off <= APPCPU_CTRL_C) { app_cpu(k); } else if (off == PRO_CACHE_CTRL || off == APP_CACHE_CTRL) { u32 c = reg(k, off); @@ -328,10 +348,13 @@ const struct esk_dev_ops esk_esp32_dport_ops = { void *esk_esp32_dport_create(struct esk_mach *s, struct esk_rf_periph *p) { struct e32_dport *k = (struct e32_dport *)esk_mach_alloc(s, sizeof *k); + size_t i; if (!k) return 0; k->s = s; k->p = p; + for (i = 0; i < NRESETS; i++) + k->rst[i] = esk_rf_periph(s->rf, resets[i].name); if (esk_mach_add_dev(s, MMU_BASE, 2u * MMU_WORDS * 4u, &mmu_ops, k) != ES_OK || esk_esp32_misc_add(s) || esk_esp32_regi2c_add(s)) return 0; diff --git a/core/mcu/esp32/soc/esp32.h b/core/mcu/esp32/soc/esp32.h index 16b4c970c..01aba4180 100644 --- a/core/mcu/esp32/soc/esp32.h +++ b/core/mcu/esp32/soc/esp32.h @@ -17,6 +17,7 @@ * apbctrl.c APB_CTRL: the CPU's and APB's clock dividers * sha.c the SHA accelerator * ledc.c the LED PWM controller + * i2c.c I2C0 and I2C1 * misc.c the random number generator, the AHB view of the * peripherals */ #ifndef ESK_MCU_ESP32_SOC_H @@ -52,6 +53,8 @@ extern const struct esk_dev_ops esk_esp32_apbctrl_ops; void *esk_esp32_apbctrl_create(struct esk_mach *s, struct esk_rf_periph *p); extern const struct esk_dev_ops esk_esp32_ledc_ops; void *esk_esp32_ledc_create(struct esk_mach *s, struct esk_rf_periph *p); +extern const struct esk_dev_ops esk_esp32_i2c_ops; +void *esk_esp32_i2c_create(struct esk_mach *s, struct esk_rf_periph *p); extern const struct esk_dev_ops esk_esp32_sha_ops; void *esk_esp32_sha_create(struct esk_mach *s, struct esk_rf_periph *p); /* the RNG and the AHB view of the peripherals (misc.c): 0, or -1 */ @@ -95,6 +98,8 @@ void esk_esp32_pins_changed(struct esk_mach *m); void esk_esp32_signal(struct esk_mach *m, int sig, int level, int oe); /* a peripheral input signal's level as the matrix or the IO MUX gives it */ int esk_esp32_signal_in(struct esk_mach *m, int sig); +/* `fn` is called when a pad's input level changed: -1 when full */ +int esk_esp32_signal_watch(struct esk_mach *m, void (*fn)(void *ctx), void *ctx); /* the flash chip (spi.c) */ struct esk_esp32_nor; diff --git a/core/mcu/esp32/soc/gpio.c b/core/mcu/esp32/soc/gpio.c index cbd07fb86..df156be19 100644 --- a/core/mcu/esp32/soc/gpio.c +++ b/core/mcu/esp32/soc/gpio.c @@ -27,7 +27,8 @@ * - A peripheral input (GPIO_FUNCy_IN_SEL_CFG) comes through the matrix * from IN_SEL's pad (0x38: 1, 0x30: 0, IN_INV_SEL inverting) when SEL * is set, else directly from the pad whose IO MUX function it is, else - * 0. + * 0 (1 for the I2C controllers' SCL and SDA). A model may watch the + * pads' input levels (esk_esp32_signal_watch). * - Interrupts (GPIO_PINn's INT_TYPE: 1 rising, 2 falling, 3 any edge, * 4 low level, 5 high level; INT_ENA bit 0 the APP_CPU's, 1 its NMI's, * 2 the PRO_CPU's, 3 its NMI's) set GPIO_STATUS (STATUS1; W1TS and @@ -44,8 +45,7 @@ * reset. * IO MUX's pads come out of reset as the datasheet's table has them (each * pad's IE and pull), not with the SVD's one reset value for all. - * Not yet: the peripherals' signals but the UARTs' (a pad routed to one - * is not driven), the RTC IO MUX (RTC_IO's pad functions, its pulls, the + * Not yet: the RTC IO MUX (RTC_IO's pad functions, its pulls, the * hold), the input filter, sleep mode (the MCU_ pad bits, SLP_SEL), the * drive strengths' effect, the pads' wake-up, the SDIO interrupt. */ #include @@ -91,6 +91,11 @@ struct e32_gpio { esk_mach_pin cfg[NPIN]; u8 sig_on[NSIG], sig_lv[NSIG], sig_oe[NSIG]; int again; + struct { + void (*fn)(void *ctx); + void *ctx; + } watch[8]; + int nwatch; }; static u32 reg(const struct esk_rf_reg *r) { return r ? (u32)r->value : 0; } @@ -281,6 +286,9 @@ static void settle(struct e32_gpio *k) esk_rf_hw(k->in, (u32)in); esk_rf_hw(k->in1, (u32)(in >> 32) & 0xffu); irqs(k, rise, fall); + if (rise | fall) + for (n = 0; n < k->nwatch; n++) + k->watch[n].fn(k->watch[n].ctx); if (k->again) goto again; k->busy = 0; @@ -315,6 +323,18 @@ int esk_esp32_signal_in(struct esk_mach *m, int sig) } if (sig == 14 && mux_sel(k, 3) == 0) /* U0RXD */ return (int)(k->input >> 3 & 1u); + /* the table's defaults: I2CEXT0/1's SCL and SDA idle high */ + return sig == 29 || sig == 30 || sig == 95 || sig == 96; +} + +int esk_esp32_signal_watch(struct esk_mach *m, void (*fn)(void *ctx), void *ctx) +{ + struct e32_gpio *k = gpio_of(m); + if (!k || k->nwatch >= (int)(sizeof k->watch / sizeof k->watch[0])) + return -1; + k->watch[k->nwatch].fn = fn; + k->watch[k->nwatch].ctx = ctx; + k->nwatch++; return 0; } diff --git a/core/mcu/esp32/soc/i2c.c b/core/mcu/esp32/soc/i2c.c new file mode 100644 index 000000000..5919bafe3 --- /dev/null +++ b/core/mcu/esp32/soc/i2c.c @@ -0,0 +1,944 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* i2c.c -- the ESP32's I2C controllers I2C0 and I2C1 (TRM v5 chapter 11) + * on the GPIO matrix's I2CEXT0_SCL and _SDA (signals 29 and 30) and + * I2CEXT1_SCL and _SDA (95 and 96), master or slave (CTR's MS_MODE), + * their interrupts on matrix sources 49 and 50. The ESP32-C3's model + * (mcu/esp32c3/i2c.c) on the ESP32's registers, timing and commands. + * + * - The controller runs from APB_CLK; its settings act as written (the + * ESP32 has no CONF_UPGATE). + * - A master runs COMD0-15 from TRANS_START: each command's byte_num + * (bits 7:0), ack_check_en (8), ack_exp (9), ack_value (10) and + * op_code (13:11: 0 RSTART, 1 WRITE, 2 READ, 3 STOP, 4 END), its + * COMMAND_DONE (bit 31) set when it is done. RSTART makes a START (once + * SCL is high) or a repeated START; WRITE sends byte_num bytes from the + * TX FIFO, the address among them, each with its ACK checked against + * ack_exp when ack_check_en is set (ACK_ERR, then a STOP and the + * commands stop); READ takes byte_num bytes into the RX FIFO, + * answering ack_value; STOP ends the transfer and the commands; END + * holds SCL low and stops (END_DETECT) until the next TRANS_START runs + * the commands from COMD0 again. Each byte a master sends or receives + * raises MASTER_TRAN_COMP and sets SR's BYTE_TRANS. + * - The bus's timing in APB_CLK cycles, as ESP-IDF's i2c_ll counts it: + * SCL low SCL_LOW_PERIOD + 1; let go, high from its rise for + * SCL_HIGH_PERIOD + 7, or + 8 with the SCL filter on at a threshold + * up to 2, + threshold + 6 above (a slave holding SCL low stretches + * the low time); SDA changes SDA_HOLD after SCL falls and is read + * SDA_SAMPLE after it rises; a START holds SDA low SCL_START_HOLD + * before SCL falls, a repeated START waits SCL_RSTART_SETUP with SCL + * high, a STOP SCL_STOP_SETUP before SDA rises and SCL_STOP_HOLD + * after it. + * - The lines are open-drain: with SCL_FORCE_OUT and SDA_FORCE_OUT the + * output is enabled throughout (the pad's PAD_DRIVER then makes it + * open-drain, as ESP-IDF configures it), without them the level and + * the enable are exchanged (low driven, high let go). A master letting + * SDA go that reads it low loses the bus (ARBITRATION_LOST, SR's + * ARB_LOST) and lets both lines go. SCL held low by someone else for + * more than TO's TIME_OUT_REG cycles ends the transfer (TIME_OUT, SR's + * TIME_OUT). + * - The TX and RX FIFOs (32 bytes each) are filled and emptied through + * DATA (ESP-IDF writes it through the AHB view, 0x6001301C and + * 0x6002701C, and reads it at the DPORT address): RXFIFO_OVF receiving + * into a full one; TX_FIFO_RST and RX_FIFO_RST empty them; TXFIFO_EMPTY + * while the TX FIFO holds fewer than TXFIFO_EMPTY_THRHD bytes and + * RXFIFO_FULL while the RX FIFO holds more than RXFIFO_FULL_THRHD; + * RXFIFO_ST and SR's counts follow them. + * - A slave answers SLAVE_ADDR (7 bits, or 10 with ADDR_10BIT_EN: the + * first byte 0x78 | its two high bits, then the low eight): it ACKs, + * puts the bytes written into the RX FIFO (with FIFO_ADDR_CFG_EN the + * first is the RAM address the bytes go to and come from), sends the + * TX FIFO's bytes when read (a NACK from the master ends them), ACKs + * a full RX FIFO (RXFIFO_OVF). SR's SLAVE_ADDRESSED and SLAVE_RW say + * how it was addressed; a STOP raises SLAVE_TRAN_COMP. + * - Both see the bus: BUS_BUSY from a START to a STOP, TRANS_COMPLETE at + * a STOP; TRANS_START when a master makes a START; ACK_REC is the last + * ACK. + * - INT_STATUS is INT_RAW and INT_ENA; INT_CLR clears. DPORT's + * PERIP_RST_EN bits 7 and 18 reset I2C0 and I2C1 (dport.c). + * Where it differs inside: the filters (SCL_ and SDA_FILTER_CFG) are not + * applied (the nets' levels are clean) beyond their share of the high + * time; SR's state fields read 0; a slave reacts at the edge it sees, not + * at its SDA_SAMPLE point. Not yet: the non-FIFO mode (NONFIFO_EN, its + * RX_REC_FULL and TX_SEND_EMPTY, the RAM read directly at 0x100), + * SAMPLE_SCL_LEVEL (sampling while SCL is low), 10-bit addressing's R/W + * check. */ +#include +#include +#include + +#include "mcu/esp32/soc/esp32.h" + +#define FS_PER_S 1000000000000000ull +#define NCMD 16 + +/* CTR */ +#define C_SDA_FORCE (1u << 0) +#define C_SCL_FORCE (1u << 1) +#define C_MS_MODE (1u << 4) +#define C_TRANS_START (1u << 5) +#define C_TX_LSB (1u << 6) +#define C_RX_LSB (1u << 7) +/* FIFO_CONF */ +#define F_ADDR_CFG (1u << 11) +#define F_RX_RST (1u << 12) +#define F_TX_RST (1u << 13) +/* INT_RAW */ +#define I_RXFIFO_FULL (1u << 0) +#define I_TXFIFO_EMPTY (1u << 1) +#define I_RXFIFO_OVF (1u << 2) +#define I_END_DETECT (1u << 3) +#define I_SLAVE_TRAN_COMP (1u << 4) +#define I_ARB_LOST (1u << 5) +#define I_MASTER_TRAN_COMP (1u << 6) +#define I_TRANS_COMPLETE (1u << 7) +#define I_TIME_OUT (1u << 8) +#define I_TRANS_START (1u << 9) +#define I_ACK_ERR (1u << 10) +/* SR */ +#define SR_ACK_REC (1u << 0) +#define SR_SLAVE_RW (1u << 1) +#define SR_TIME_OUT (1u << 2) +#define SR_ARB_LOST (1u << 3) +#define SR_BUS_BUSY (1u << 4) +#define SR_ADDRESSED (1u << 5) +#define SR_BYTE_TRANS (1u << 6) + +enum { OP_RSTART = 0, OP_WRITE = 1, OP_READ = 2, OP_STOP = 3, OP_END = 4 }; + +/* the master's step */ +enum { + P_IDLE, + P_WAIT_BUS, /* SCL low (someone holds it): a START waits for it */ + P_START, /* SDA low: SCL falls after the START hold */ + P_LOW0, /* SCL low: SDA changes after the hold */ + P_LOW1, /* SCL low: let go of it at the low period's end */ + P_WAITHI, /* SCL let go, waiting for it to rise */ + P_HIGH0, /* SCL high: SDA read at the sample point */ + P_HIGH1, /* SCL high: SCL falls at the high period's end */ + P_RS_SETUP, /* SCL high: SDA falls for the repeated START */ + P_STOP_SETUP, /* SCL high: SDA rises for the STOP */ + P_STOP_HOLD, /* the bus free time after a STOP */ + P_HELD /* END: SCL held low */ +}; + +/* what a clock pulse carries */ +enum { K_DOUT, K_DIN, K_AIN, K_AOUT, K_RS, K_STOP }; + +/* the slave's state */ +enum { S_IDLE, S_ADDR, S_ADDR2, S_RX, S_TX, S_WAIT }; + +struct e32_i2c { + struct esk_mach *s; + struct esk_rf_periph *p; + struct esk_rf_reg *ctr, *sr, *to, *saddr, *fifo_st, *fifo_conf, *raw, *ena, *st; + struct esk_rf_reg *low, *high, *hold, *sample, *sth, *rss, *sph, *sps, *sclf; + struct esk_rf_reg *comd[NCMD]; + int source, sig_scl, sig_sda; + /* the FIFOs' RAM */ + u8 txram[32], rxram[32]; + u32 twr, trd, tcnt, rwr, rrd, rcnt; + /* the lines: what it drives (1 lets go), what it sees */ + int scl_o, sda_o, scl_i, sda_i; + /* the next event: an absolute cycle and fs past it */ + int ev; + u64 ev_c, ev_f, cur_c, cur_f; + /* the master */ + int phase, kind, waiting_to, ci, op, bitn; + u32 cmd, nbytes, byte; + int sda_next; + /* the slave */ + int sstate, sbit, srw, sfirst, spend, spend_sda; + u32 sbyte; + int clocked, level, in_step; +}; + +static u32 reg(const struct esk_rf_reg *r) { return (u32)r->value; } + +static void update(struct e32_i2c *k) +{ + u32 st = reg(k->raw) & reg(k->ena) & 0x1fffu; + int level = st != 0; + esk_rf_hw(k->st, st); + if (level != k->level) { + k->level = level; + esk_esp32_source(k->s, k->source, level); + } +} + +static void raise(struct e32_i2c *k, u32 bits) +{ + esk_rf_hw(k->raw, reg(k->raw) | bits); + update(k); +} + +static void sr_set(struct e32_i2c *k, u32 bits, int on) +{ + esk_rf_hw(k->sr, on ? reg(k->sr) | bits : reg(k->sr) & ~bits); +} + +static u32 reverse8(u32 b) +{ + u32 r = 0; + int i; + for (i = 0; i < 8; i++) + r |= ((b >> i) & 1u) << (7 - i); + return r; +} + +/* ---- time ------------------------------------------------------------------ */ + +static double apb_fs(const struct e32_i2c *k) +{ + u32 hz = esk_esp32_apb_hz(k->s); + return hz ? (double)FS_PER_S / (double)hz : 0; +} + +static u64 fs_per_cycle(const struct e32_i2c *k) +{ + u32 hz = esk_mach_core_hz(k->s); + return hz ? FS_PER_S / hz : 0; +} + +/* the event `n` APB_CLK cycles after the present */ +static void after(struct e32_i2c *k, u32 n) +{ + u64 fpc = fs_per_cycle(k), d; + double af = apb_fs(k); + if (!fpc || af <= 0) { + k->ev = 0; + return; + } + d = (u64)((double)n * af + 0.5) + k->cur_f; + k->ev_c = k->cur_c + d / fpc; + k->ev_f = d % fpc; + k->ev = 1; + k->s->bus.touched = 1; /* the machine's next event may be this one */ +} + +/* the present: a register write's, an edge's or an event's time */ +static void now_at(struct e32_i2c *k, u64 cycle) +{ + k->cur_c = cycle; + k->cur_f = 0; +} + +static void clocking(struct e32_i2c *k) +{ + esk_rf_clock(k->p, &k->clocked, k->ev); +} + +/* ---- the lines ------------------------------------------------------------------ */ + +static void lines(struct e32_i2c *k, int scl, int sda) +{ + u32 c = reg(k->ctr); + int force_scl = (c & C_SCL_FORCE) != 0, force_sda = (c & C_SDA_FORCE) != 0; + k->scl_o = scl; + k->sda_o = sda; + esk_esp32_signal(k->s, k->sig_scl, scl, force_scl || !scl); + esk_esp32_signal(k->s, k->sig_sda, sda, force_sda || !sda); +} + +static int scl_in(struct e32_i2c *k) { return esk_esp32_signal_in(k->s, k->sig_scl); } +static int sda_in(struct e32_i2c *k) { return esk_esp32_signal_in(k->s, k->sig_sda); } + +/* ---- the FIFOs ------------------------------------------------------------------- */ + +static void fifo_status(struct e32_i2c *k) +{ + u32 conf = reg(k->fifo_conf), raw = reg(k->raw); + esk_rf_hw(k->sr, (reg(k->sr) & ~(0x3fu << 8 | 0x3fu << 18)) | (k->rcnt & 0x3fu) << 8 | (k->tcnt & 0x3fu) << 18); + esk_rf_hw(k->fifo_st, (k->rrd & 31u) | (k->rwr & 31u) << 5 | (k->trd & 31u) << 10 | (k->twr & 31u) << 15); + if (k->tcnt < ((conf >> 5) & 31u)) + raw |= I_TXFIFO_EMPTY; + if (k->rcnt > (conf & 31u)) + raw |= I_RXFIFO_FULL; + esk_rf_hw(k->raw, raw); + update(k); +} + +static u32 tx_pop(struct e32_i2c *k) +{ + u32 b = k->txram[k->trd & 31u]; + k->trd = (k->trd + 1u) & 31u; + if (k->tcnt) + k->tcnt--; + fifo_status(k); + return b; +} + +static void rx_push(struct e32_i2c *k, u32 b) +{ + if (k->rcnt >= 32u) { + raise(k, I_RXFIFO_OVF); + return; + } + k->rxram[k->rwr & 31u] = (u8)(b & 0xffu); + k->rwr = (k->rwr + 1u) & 31u; + k->rcnt++; + fifo_status(k); +} + +/* ---- the master ------------------------------------------------------------------ */ + +static u32 t_low(const struct e32_i2c *k) { return (reg(k->low) & 0x3fffu) + 1u; } + +static u32 t_high(const struct e32_i2c *k) +{ + u32 f = reg(k->sclf), thres = f & 7u, extra = 7u; + if (f & 8u) + extra = thres <= 2u ? 8u : thres + 6u; + return (reg(k->high) & 0x3fffu) + extra; +} + +static u32 at_least1(u32 v) { return v ? v : 1u; } +static u32 t_hold(const struct e32_i2c *k) { return at_least1(reg(k->hold) & 0x3ffu); } +static u32 t_sample(const struct e32_i2c *k) { return at_least1(reg(k->sample) & 0x3ffu); } + +static void master_idle(struct e32_i2c *k) +{ + k->phase = P_IDLE; + k->ev = 0; + lines(k, 1, 1); +} + +static void cmd_done(struct e32_i2c *k) +{ + if (k->ci < NCMD) + esk_rf_hw(k->comd[k->ci], reg(k->comd[k->ci]) | 1u << 31); +} + +/* a clock pulse from SCL's fall: SDA's level for it after the hold */ +static void begin_pulse(struct e32_i2c *k, int kind, int sda) +{ + u32 hold = t_hold(k), low = t_low(k); + k->kind = kind; + k->sda_next = sda; + k->phase = P_LOW0; + after(k, hold < low ? hold : low - 1u); +} + +static void begin_byte(struct e32_i2c *k) +{ + k->bitn = 0; + sr_set(k, SR_BYTE_TRANS, 0); + if (k->op == OP_WRITE) { + u32 b = tx_pop(k); + if (reg(k->ctr) & C_TX_LSB) /* the bits reversed: still sent from bit 7 */ + b = reverse8(b); + k->byte = b; + begin_pulse(k, K_DOUT, (int)((b >> 7) & 1u)); + } else { + k->byte = 0; + begin_pulse(k, K_DIN, 1); + } +} + +/* the next command, SCL low (or the bus idle for a first START) */ +static void next_command(struct e32_i2c *k) +{ + for (;;) { + u32 c; + if (k->ci >= NCMD) { + k->phase = P_HELD; + k->ev = 0; + return; + } + c = reg(k->comd[k->ci]); + k->cmd = c; + k->op = (int)((c >> 11) & 7u); + k->nbytes = c & 0xffu; + switch (k->op) { + case OP_RSTART: + if (k->phase == P_IDLE || k->phase == P_STOP_HOLD) { + if (!scl_in(k)) { + k->phase = P_WAIT_BUS; + k->ev = 0; + return; + } + k->phase = P_START; + raise(k, I_TRANS_START); + lines(k, 1, 0); + after(k, at_least1(reg(k->sth) & 0x3ffu)); + } else { + begin_pulse(k, K_RS, 1); + } + return; + case OP_WRITE: + case OP_READ: + if (!k->nbytes) { + cmd_done(k); + k->ci++; + continue; + } + begin_byte(k); + return; + case OP_STOP: + begin_pulse(k, K_STOP, 0); + return; + case OP_END: + cmd_done(k); + k->phase = P_HELD; + k->ev = 0; + raise(k, I_END_DETECT); + return; + default: /* not a command: nothing to do */ + cmd_done(k); + k->ci++; + continue; + } + } +} + +/* SCL just fell after a pulse: what comes next */ +static void pulse_done(struct e32_i2c *k) +{ + switch (k->kind) { + case K_DOUT: + if (++k->bitn < 8) { + begin_pulse(k, K_DOUT, (int)((k->byte >> (7 - k->bitn)) & 1u)); + return; + } + begin_pulse(k, K_AIN, 1); + return; + case K_DIN: + if (++k->bitn < 8) { + begin_pulse(k, K_DIN, 1); + return; + } + begin_pulse(k, K_AOUT, (int)((k->cmd >> 10) & 1u)); + return; + case K_AIN: { + int ack = (reg(k->sr) & SR_ACK_REC) != 0; + sr_set(k, SR_BYTE_TRANS, 1); + raise(k, I_MASTER_TRAN_COMP); + if ((k->cmd & (1u << 8)) && ack != (int)((k->cmd >> 9) & 1u)) { + raise(k, I_ACK_ERR); + k->ci = NCMD; /* the commands stop */ + begin_pulse(k, K_STOP, 0); + return; + } + break; + } + case K_AOUT: { + u32 b = k->byte; + if (reg(k->ctr) & C_RX_LSB) + b = reverse8(b); + rx_push(k, b); + sr_set(k, SR_BYTE_TRANS, 1); + raise(k, I_MASTER_TRAN_COMP); + break; + } + case K_RS: + cmd_done(k); + k->ci++; + next_command(k); + return; + default: + return; + } + /* a byte done */ + if (--k->nbytes > 0) { + begin_byte(k); + return; + } + cmd_done(k); + k->ci++; + next_command(k); +} + +/* SCL is high: the high time counts from its rise */ +static void high_begins(struct e32_i2c *k) +{ + u32 s = t_sample(k), h = t_high(k); + k->waiting_to = 0; + switch (k->kind) { + case K_RS: + k->phase = P_RS_SETUP; + after(k, at_least1(reg(k->rss) & 0x3ffu)); + return; + case K_STOP: + k->phase = P_STOP_SETUP; + after(k, at_least1(reg(k->sps) & 0x3ffu)); + return; + default: + k->phase = P_HIGH0; + after(k, s < h ? s : h - 1u); + return; + } +} + +static void master_step(struct e32_i2c *k) +{ + switch (k->phase) { + case P_START: + lines(k, 0, 0); + cmd_done(k); + k->ci++; + next_command(k); + return; + case P_LOW0: + lines(k, 0, k->sda_next); + k->phase = P_LOW1; + after(k, t_low(k) > t_hold(k) ? t_low(k) - t_hold(k) : 1u); + return; + case P_LOW1: + k->phase = P_WAITHI; + k->ev = 0; + lines(k, 1, k->sda_o); + if (k->phase == P_WAITHI && scl_in(k)) { + high_begins(k); + } else if (k->phase == P_WAITHI) { /* SCL held: the timeout */ + k->waiting_to = 1; + after(k, at_least1(reg(k->to) & 0xfffffu)); + } + return; + case P_WAITHI: /* the timeout came first */ + if (k->waiting_to) { + sr_set(k, SR_TIME_OUT, 1); + raise(k, I_TIME_OUT); + master_idle(k); + } + return; + case P_HIGH0: { + int sda = sda_in(k); + u32 h = t_high(k), s = t_sample(k); + if (k->kind == K_DOUT && k->sda_o && !sda) { + sr_set(k, SR_ARB_LOST, 1); + raise(k, I_ARB_LOST); + k->ci = NCMD; + master_idle(k); + return; + } + if (k->kind == K_DIN) + k->byte = k->byte << 1 | (u32)sda; + if (k->kind == K_AIN) + sr_set(k, SR_ACK_REC, sda); + k->phase = P_HIGH1; + after(k, h > s ? h - s : 1u); + return; + } + case P_HIGH1: + lines(k, 0, k->sda_o); + pulse_done(k); + return; + case P_RS_SETUP: + raise(k, I_TRANS_START); + lines(k, 1, 0); + k->phase = P_START; + after(k, at_least1(reg(k->sth) & 0x3ffu)); + return; + case P_STOP_SETUP: + lines(k, 1, 1); + cmd_done(k); + k->phase = P_STOP_HOLD; + after(k, at_least1(reg(k->sph) & 0x3fffu)); + return; + case P_STOP_HOLD: + k->phase = P_IDLE; + k->ev = 0; + return; + default: + return; + } +} + +/* ---- the slave -------------------------------------------------------------------- */ + +/* SDA's next level, SDA_HOLD after SCL fell */ +static void slave_sda(struct e32_i2c *k, int level) +{ + k->spend = 1; + k->spend_sda = level; + after(k, t_hold(k)); +} + +static u32 slave_tx_byte(struct e32_i2c *k) +{ + u32 b = k->txram[k->trd & 31u]; + k->trd = (k->trd + 1u) & 31u; + if (k->tcnt) + k->tcnt--; + fifo_status(k); + return b; +} + +static void slave_rise(struct e32_i2c *k) +{ + int sda = k->sda_i; + switch (k->sstate) { + case S_ADDR: + case S_ADDR2: + case S_RX: + if (k->sbit < 8) { + k->sbyte = k->sbyte << 1 | (u32)sda; + k->sbit++; + } else { + k->sbit = 9; + } + return; + case S_TX: + if (k->sbit < 8) { + k->sbit++; + } else { + k->sbit = 9; + sr_set(k, SR_ACK_REC, sda); + if (sda) /* the master's NACK: the last byte */ + k->sstate = S_WAIT; + } + return; + default: + return; + } +} + +static void slave_fall(struct e32_i2c *k) +{ + u32 sa = reg(k->saddr); + switch (k->sstate) { + case S_ADDR: + if (k->sbit == 8) { + if ((k->sbyte >> 1) != (sa & 0x7fu)) { + k->sstate = S_WAIT; + return; + } + k->srw = (int)(k->sbyte & 1u); + sr_set(k, SR_ADDRESSED, 1); + sr_set(k, SR_SLAVE_RW, k->srw); + k->sfirst = 1; + slave_sda(k, 0); + return; + } + if (k->sbit == 9) { /* after the address's ACK */ + k->sbit = 0; + k->sbyte = 0; + if ((sa & (1u << 31)) && !k->srw) { + k->sstate = S_ADDR2; + slave_sda(k, 1); + return; + } + if (k->srw) { + u32 b = slave_tx_byte(k); + k->sstate = S_TX; + k->sbyte = b; + slave_sda(k, (int)((b >> 7) & 1u)); + } else { + k->sstate = S_RX; + slave_sda(k, 1); + } + } + return; + case S_ADDR2: + if (k->sbit == 8) { + if ((k->sbyte & 0xffu) != ((sa >> 7) & 0xffu)) { + k->sstate = S_WAIT; + return; + } + slave_sda(k, 0); + return; + } + if (k->sbit == 9) { + k->sbit = 0; + k->sbyte = 0; + k->sstate = S_RX; + slave_sda(k, 1); + } + return; + case S_RX: + if (k->sbit == 8) { + u32 b = k->sbyte & 0xffu; + if (reg(k->ctr) & C_RX_LSB) + b = reverse8(b); + if (k->sfirst && (reg(k->fifo_conf) & F_ADDR_CFG)) /* the RAM address */ + k->rwr = k->trd = b & 31u; + else + rx_push(k, b); + k->sfirst = 0; + sr_set(k, SR_BYTE_TRANS, 1); + slave_sda(k, 0); + return; + } + if (k->sbit == 9) { + k->sbit = 0; + k->sbyte = 0; + sr_set(k, SR_BYTE_TRANS, 0); + slave_sda(k, 1); + } + return; + case S_TX: + if (k->sbit < 8) { + slave_sda(k, (int)((k->sbyte >> (7 - k->sbit)) & 1u)); + return; + } + if (k->sbit == 8) { /* the master's ACK: let SDA go */ + sr_set(k, SR_BYTE_TRANS, 1); + slave_sda(k, 1); + return; + } + { /* the next byte */ + u32 b; + k->sbit = 0; + sr_set(k, SR_BYTE_TRANS, 0); + b = slave_tx_byte(k); + k->sbyte = b; + slave_sda(k, (int)((b >> 7) & 1u)); + } + return; + default: + return; + } +} + +static void slave_step(struct e32_i2c *k) +{ + if (k->spend) { + k->spend = 0; + lines(k, 1, k->spend_sda); + } +} + +/* ---- the bus as seen -------------------------------------------------------------- */ + +static void watch(void *ctx) +{ + struct e32_i2c *k = (struct e32_i2c *)ctx; + struct esk_mach *s = k->s; + int scl = scl_in(k), sda = sda_in(k), master = (reg(k->ctr) & C_MS_MODE) != 0; + u64 lag = (u64)s->input_lead + s->output_lag; + if (scl == k->scl_i && sda == k->sda_i) + return; + if (!k->in_step) + now_at(k, s->cycles > lag ? s->cycles - lag : 0); + /* START and STOP: SDA moving while SCL stays high */ + if (scl && k->scl_i && sda != k->sda_i) { + k->sda_i = sda; + if (!sda) { + sr_set(k, SR_BUS_BUSY, 1); + if (!master) { + k->sstate = S_ADDR; + k->sbit = 0; + k->sbyte = 0; + k->spend = 0; + if (k->sda_o == 0 || k->scl_o == 0) + lines(k, 1, 1); + } + } else { /* a STOP */ + sr_set(k, SR_BUS_BUSY, 0); + raise(k, I_TRANS_COMPLETE); + if (!master) { + k->sstate = S_IDLE; + sr_set(k, SR_ADDRESSED, 0); + raise(k, I_SLAVE_TRAN_COMP); + if (k->sda_o == 0) + lines(k, 1, 1); + } + } + } + k->sda_i = sda; + if (scl != k->scl_i) { + k->scl_i = scl; + if (master) { + if (scl && k->phase == P_WAITHI) { + k->ev = 0; + high_begins(k); + } else if (scl && k->phase == P_WAIT_BUS) { + k->phase = P_IDLE; + next_command(k); + } + } else if (scl) { + slave_rise(k); + } else { + slave_fall(k); + } + } + clocking(k); +} + +/* ---- registers ---------------------------------------------------------------------- */ + +static void fsm_reset(struct e32_i2c *k) +{ + k->phase = P_IDLE; + k->ev = 0; + k->sstate = S_IDLE; + k->spend = 0; + k->waiting_to = 0; + lines(k, 1, 1); + k->scl_i = scl_in(k); + k->sda_i = sda_in(k); +} + +static void i2c_tick(void *ctx, u32 cycles) +{ + struct e32_i2c *k = (struct e32_i2c *)ctx; + struct esk_mach *s = k->s; + (void)cycles; + while (k->ev && k->ev_c <= s->cycles) { + k->ev = 0; + k->cur_c = k->ev_c; + k->cur_f = k->ev_f; + s->output_lag = (u32)(s->cycles - k->ev_c); + k->in_step = 1; + if (reg(k->ctr) & C_MS_MODE) + master_step(k); + else + slave_step(k); + k->in_step = 0; + s->output_lag = 0; + } + clocking(k); +} + +static int i2c_next(void *ctx, u32 *cycles) +{ + struct e32_i2c *k = (struct e32_i2c *)ctx; + u64 d; + if (!k->ev) + return 0; + d = k->ev_c > k->s->cycles ? k->ev_c - k->s->cycles : 1u; + *cycles = d > 0xffffffffu ? 0xffffffffu : (u32)d; + return 1; +} + +static u32 i2c_read(void *ctx, u32 off, int n) +{ + struct e32_i2c *k = (struct e32_i2c *)ctx; + if ((off & ~3u) == 0x1Cu) { /* DATA: the RX FIFO */ + u32 b = k->rxram[k->rrd & 31u]; + if (!k->rcnt) + return b; + k->rrd = (k->rrd + 1u) & 31u; + k->rcnt--; + fifo_status(k); + return b; + } + return esk_rf_read(k->p, off, n); +} + +static void i2c_write(void *ctx, u32 off, int n, u32 v) +{ + struct e32_i2c *k = (struct e32_i2c *)ctx; + u32 w = off & ~3u, was = reg(k->ctr); + now_at(k, k->s->cycles); + switch (w) { + case 0x1Cu: /* DATA: the TX FIFO */ + if (k->tcnt >= 32u) + return; /* full: the byte is lost */ + k->txram[k->twr & 31u] = (u8)(v & 0xffu); + k->twr = (k->twr + 1u) & 31u; + k->tcnt++; + fifo_status(k); + return; + case 0x24u: /* INT_CLR */ + esk_rf_hw(k->raw, reg(k->raw) & ~v); + fifo_status(k); /* the FIFO levels' interrupts raise again */ + return; + case 0x08u: + case 0x14u: + case 0x2Cu: + return; /* SR, RXFIFO_ST, INT_STATUS */ + default: + break; + } + esk_rf_write(k->p, off, n, v); + if (w == 0x20u || w == 0x28u) + update(k); + if (w == esk_rf_off(k->fifo_conf)) { + if (v & F_TX_RST) + k->twr = k->trd = k->tcnt = 0; + if (v & F_RX_RST) + k->rwr = k->rrd = k->rcnt = 0; + fifo_status(k); + } + if (w == esk_rf_off(k->ctr)) { + u32 c = reg(k->ctr); + if ((c ^ was) & (C_SCL_FORCE | C_SDA_FORCE | C_MS_MODE)) + lines(k, k->scl_o, k->sda_o); + if ((c & C_TRANS_START) && (c & C_MS_MODE) && apb_fs(k) > 0) { + int held = k->phase == P_HELD; + sr_set(k, SR_ARB_LOST | SR_TIME_OUT, 0); + k->ci = 0; + if (held) { /* SCL is low: the next command's pulse from now */ + k->phase = P_LOW0; + next_command(k); + } else if (k->phase == P_IDLE || k->phase == P_STOP_HOLD) { + k->phase = P_IDLE; + next_command(k); + } + } + esk_rf_hw(k->ctr, c & ~C_TRANS_START); /* it clears itself */ + } + clocking(k); +} + +static void i2c_reset(void *ctx) +{ + struct e32_i2c *k = (struct e32_i2c *)ctx; + k->twr = k->trd = k->tcnt = k->rwr = k->rrd = k->rcnt = 0; + k->level = 0; + esk_esp32_source(k->s, k->source, 0); + k->clocked = 0; + fsm_reset(k); + fifo_status(k); + update(k); + clocking(k); +} + +const struct esk_dev_ops esk_esp32_i2c_ops = { + "esp32-i2c", i2c_read, i2c_write, i2c_reset, i2c_tick, i2c_next, +}; + +void *esk_esp32_i2c_create(struct esk_mach *s, struct esk_rf_periph *p) +{ + struct e32_i2c *k = (struct e32_i2c *)esk_mach_alloc(s, sizeof *k); + static const struct { + size_t at; + const char *name; + } regs[] = { + { offsetof(struct e32_i2c, ctr), "CTR" }, + { offsetof(struct e32_i2c, sr), "SR" }, + { offsetof(struct e32_i2c, to), "TO" }, + { offsetof(struct e32_i2c, saddr), "SLAVE_ADDR" }, + { offsetof(struct e32_i2c, fifo_st), "RXFIFO_ST" }, + { offsetof(struct e32_i2c, fifo_conf), "FIFO_CONF" }, + { offsetof(struct e32_i2c, raw), "INT_RAW" }, + { offsetof(struct e32_i2c, ena), "INT_ENA" }, + { offsetof(struct e32_i2c, st), "INT_STATUS" }, + { offsetof(struct e32_i2c, low), "SCL_LOW_PERIOD" }, + { offsetof(struct e32_i2c, high), "SCL_HIGH_PERIOD" }, + { offsetof(struct e32_i2c, hold), "SDA_HOLD" }, + { offsetof(struct e32_i2c, sample), "SDA_SAMPLE" }, + { offsetof(struct e32_i2c, sth), "SCL_START_HOLD" }, + { offsetof(struct e32_i2c, rss), "SCL_RSTART_SETUP" }, + { offsetof(struct e32_i2c, sph), "SCL_STOP_HOLD" }, + { offsetof(struct e32_i2c, sps), "SCL_STOP_SETUP" }, + { offsetof(struct e32_i2c, sclf), "SCL_FILTER_CFG" }, + }; + char name[24]; + size_t i; + int unit; + if (!k) + return 0; + memset(k, 0, sizeof *k); + k->s = s; + k->p = p; + unit = strcmp(p->name, "I2C1") == 0; + k->source = unit ? 50 : 49; + k->sig_scl = unit ? 95 : 29; + k->sig_sda = unit ? 96 : 30; + for (i = 0; i < sizeof regs / sizeof regs[0]; i++) { + struct esk_rf_reg *r = esk_rf_reg(p, regs[i].name); + if (!r) + return 0; + memcpy((char *)k + regs[i].at, &r, sizeof r); + } + for (i = 0; i < NCMD; i++) { + snprintf(name, sizeof name, "COMD%d", (int)i); + if (!(k->comd[i] = esk_rf_reg(p, name))) + return 0; + } + if (esk_esp32_signal_watch(s, watch, k) != 0) + return 0; + return k; +} diff --git a/core/mcu/models.c b/core/mcu/models.c index 314801791..52c983242 100644 --- a/core/mcu/models.c +++ b/core/mcu/models.c @@ -72,6 +72,7 @@ static const struct esk_rf_model_type model_types[] = { { "esp32-timg", &esk_esp32_timg_ops, esk_esp32_timg_create }, { "esp32-sha", &esk_esp32_sha_ops, esk_esp32_sha_create }, { "esp32-ledc", &esk_esp32_ledc_ops, esk_esp32_ledc_create }, + { "esp32-i2c", &esk_esp32_i2c_ops, esk_esp32_i2c_create }, { "esp32-iomux", &esk_esp32_iomux_ops, esk_esp32_iomux_create }, { "esp32c3-intmtx", &esk_esp32c3_intmtx_ops, esk_esp32c3_intmtx_create }, { "esp32c3-uart", &esk_esp32c3_uart_ops, esk_esp32c3_uart_create }, diff --git a/docs/STATUS.md b/docs/STATUS.md index c563bbea0..511f80037 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -14,10 +14,11 @@ Espressif's ROM (ECO3), ESP-IDF v5.4's second-stage bootloader and its hello_world running unmodified on both cores at 160 MHz to `Hello world!` and through `esp_restart` (rst:0xc SW_CPU_RESET). Evidence: `test_lx6` (4,332 checks), the board's three fixtures, and the ESP-IDF vendor -scenarios `esp32_hello_world` (9/9), `esp32_blink` (6/6) and -`esp32_ledc_basic` (2/2). Not yet: Wi-Fi and Bluetooth, the +scenarios `esp32_hello_world` (9/9), `esp32_blink` (6/6), +`esp32_ledc_basic` (2/2) and `esp32_i2c_eeprom` (3/3, I2C0 writing and +reading a 24LC256 at 400 kHz). Not yet: Wi-Fi and Bluetooth, the double-precision accelerator's instructions, the peripherals the -description's fidelity lists (I2C, I2S, SPI2/3, RMT, ADC, ...), +description's fidelity lists (I2S, SPI2/3, RMT, ADC, ...), sleep. The standalone harness below remains as its own qualification fixtures. diff --git a/docs/coverage.md b/docs/coverage.md index 00c64da55..99d40d33f 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -8,7 +8,7 @@ evidence for each model is in [STATUS.md](STATUS.md). | MCU | Modelled | Registers only | Missing | Peripherals | |---|---|---|---|---| -| ESP32 | 15 | 0 | 30 | 45 | +| ESP32 | 17 | 0 | 28 | 45 | | ESP32-C3 | 35 | 0 | 0 | 35 | | ATmega328P | 15 | 0 | 0 | 15 | | PIC24FJ1024GB610 | 11 | 4 | 17 | 32 | @@ -23,7 +23,7 @@ evidence for each model is in [STATUS.md](STATUS.md). ## ESP32 (`espressif/esp32`) Boards: ESP32-DevKitC V4. -15 of 45 peripherals modelled, 0 registers only, 30 missing. +17 of 45 peripherals modelled, 0 registers only, 28 missing. | Peripheral | Model | State | |---|---|---| @@ -47,7 +47,7 @@ Boards: ESP32-DevKitC V4. | UHCI1 | — | **missing** | | I2S0 | — | **missing** | | UART1 | `esp32-uart` | modelled | -| I2C0 | — | **missing** | +| I2C0 | `esp32-i2c` | modelled | | UHCI0 | — | **missing** | | SLCHOST | — | **missing** | | RMT | — | **missing** | @@ -63,7 +63,7 @@ Boards: ESP32-DevKitC V4. | SPI2 | — | **missing** | | SPI3 | — | **missing** | | APB_CTRL | `esp32-apbctrl` | modelled | -| I2C1 | — | **missing** | +| I2C1 | `esp32-i2c` | modelled | | SDHOST | — | **missing** | | EMAC_DMA | — | **missing** | | EMAC_EXT | — | **missing** | @@ -73,7 +73,7 @@ Boards: ESP32-DevKitC V4. | I2S1 | — | **missing** | | UART2 | `esp32-uart` | modelled | -Declared omissions: Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written); the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept; PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts; the peripherals without a model yet: I2C0/1, I2S0/1, SPI2/3 (HSPI, VSPI), RMT, MCPWM, PCNT, TWAI, SDMMC, EMAC, the SAR ADCs, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption; sleep modes (light and deep sleep, the power domains), brown-out; the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection. +Declared omissions: Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written); the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept; PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts; I2C: the non-FIFO mode (NONFIFO_EN, RX_REC_FULL, TX_SEND_EMPTY), SAMPLE_SCL_LEVEL, the input filters (only their share of the SCL high time), 10-bit addressing's R/W check; the peripherals without a model yet: I2S0/1, SPI2/3 (HSPI, VSPI), RMT, MCPWM, PCNT, TWAI, SDMMC, EMAC, the SAR ADCs, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption; sleep modes (light and deep sleep, the power domains), brown-out; the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection. ## ESP32-C3 (`espressif/esp32c3`) diff --git a/tests/vendor/esp-idf/examples/esp32_i2c_eeprom/project.json b/tests/vendor/esp-idf/examples/esp32_i2c_eeprom/project.json new file mode 100644 index 000000000..b6da657ea --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_i2c_eeprom/project.json @@ -0,0 +1,126 @@ +{ + "schema_version": 1, + "kind": "project", + "name": "esp-idf-esp32_i2c_eeprom", + "description": "ESP-IDF's i2c/i2c_eeprom on the ESP32-DevKitC V4: a 24LC256 at 0x50 (A0-A2 and WP grounded) on SCL IO4 and SDA IO5 at 400 kHz, the example's defaults, with 4.7 kohm pull-ups to 3V3 (the example enables no internal pull-ups). The part's write cycle is set to 2 ms, a part faster than the datasheet's 5 ms maximum: the example waits pdMS_TO_TICKS(10), one tick at FreeRTOS's 100 Hz, which ends at the next tick, and a part taking its maximum would NACK the read, on a real bench too.", + "policy": "electrical", + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc", + "layout": { + "x": 360, + "y": 120 + }, + "firmware": { + "image": "flash.bin", + "format": "bin" + } + } + ], + "components": [ + { + "id": "USB", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5 + }, + "layout": { + "x": 120, + "y": 160 + } + }, + { + "id": "GND1", + "component": "emblink/gnd", + "layout": { + "x": 120, + "y": 320 + } + }, + { + "id": "U2", + "component": "emblink/eeprom_24lc256", + "params": { + "view_at": 16, + "twr": "2m" + }, + "layout": { + "x": 120, + "y": 460 + } + }, + { + "id": "R1", + "component": "emblink/resistor", + "params": { + "resistance": 4700 + }, + "layout": { + "x": 300, + "y": 440 + } + }, + { + "id": "R2", + "component": "emblink/resistor", + "params": { + "resistance": 4700 + }, + "layout": { + "x": 340, + "y": 440 + } + } + ], + "nets": { + "USB5V": [ + "USB.+", + "B1.VBUS" + ], + "GND": [ + "USB.-", + "GND1.GND", + "B1.GND1", + "U2.GND", + "U2.A0", + "U2.A1", + "U2.A2", + "U2.WP" + ], + "3V3": [ + "B1.3V3", + "U2.VCC", + "R1.1", + "R2.1" + ], + "I2C_SCL": [ + "B1.IO4", + "U2.SCL", + "R1.2" + ], + "I2C_SDA": [ + "B1.IO5", + "U2.SDA", + "R2.2" + ] + }, + "probes": [ + { + "id": "console", + "target": "serial:B1", + "kind": "serial" + }, + { + "id": "eeprom", + "target": "output:U2.view", + "kind": "value" + }, + { + "id": "cycles", + "target": "output:U2.cycles", + "kind": "value" + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/examples/esp32_i2c_eeprom/scenario.json b/tests/vendor/esp-idf/examples/esp32_i2c_eeprom/scenario.json new file mode 100644 index 000000000..d2d891de0 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_i2c_eeprom/scenario.json @@ -0,0 +1,34 @@ +{ + "schema_version": 1, + "kind": "scenario", + "name": "esp32_i2c_eeprom", + "until": "1500ms", + "description": "i2c_eeprom on the ESP32: 0..47 written in one transaction (two address bytes, 48 data, a page from 0x0010 within the 64-byte page), 10 ms for the write cycle, then read back with a repeated START and printed 16 to a line, again every 50 ticks (500 ms). I2C0 runs it as one command and an END at a time (the ESP32's STOP needs its own transfer, SOC_I2C_STOP_INDEPENDENT), from APB_CLK at 400 kHz.", + "inputs": [], + "assertions": [ + { + "id": "stored", + "at": "1s", + "probe": "eeprom", + "expect": { + "contains": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2FFFFF" + } + }, + { + "id": "read-back", + "at": "1s", + "probe": "console", + "expect": { + "contains": "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f \r\n10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f \r\n20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f \r\n" + } + }, + { + "id": "again", + "at": "1400ms", + "probe": "cycles", + "expect": { + "equals": 3 + } + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/run.py b/tests/vendor/esp-idf/run.py index 55b9e73b1..d5cd3d58d 100644 --- a/tests/vendor/esp-idf/run.py +++ b/tests/vendor/esp-idf/run.py @@ -74,6 +74,7 @@ "esp32_hello_world": "examples/get-started/hello_world", "esp32_blink": "examples/get-started/blink", "esp32_ledc_basic": "examples/peripherals/ledc/ledc_basic", + "esp32_i2c_eeprom": "examples/peripherals/i2c/i2c_eeprom", } # the target an example is built for, when not the ESP32-C3 (its board in # examples//project.json), and the toolchain that target needs @@ -81,6 +82,7 @@ "esp32_hello_world": "esp32", "esp32_blink": "esp32", "esp32_ledc_basic": "esp32", + "esp32_i2c_eeprom": "esp32", } TOOLCHAINS = {"esp32c3": "riscv32-esp-elf-gcc", "esp32": "xtensa-esp32-elf-gcc"} # examples built only for another's project (with examples//'s From e290d3f45127033ea09d0c20175e4a08799d4958 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Sun, 8 Nov 2026 04:15:06 +0000 Subject: [PATCH 49/57] ESP32: the catalog checks pass tools/validate.py rejected the ESP32's description: the schema had no xtensa backend and capped a port at 32 pins (the ESP32's GPIO bank has 40), Espressif's SVD lists fields that overlap (a whole-register STATUS beside its parts, two names for one bit), there was no gcc-xtensa toolchain profile, and the fidelity's validation entries were prose. svd2regs.py gains --resolve-overlaps (a field containing another taken out, of fields over the same bits the first kept, recorded in source); the ESP32's register file is regenerated with it, WIFI_CLK_EN kept as one field and the SVD's stray fields in CPUSDIO_INT1 and SAR_MEAS_WAIT2 reserved. The board and the MCU have their French text. --- catalog/boards/esp32_devkitc/board.json | 24 +- catalog/mcus/espressif/esp32/mcu.json | 48 +++- catalog/mcus/espressif/esp32/registers.json | 288 +------------------- catalog/toolchains/gcc-xtensa.json | 69 +++++ schemas/mcu.schema.json | 4 +- tools/importers/svd2regs.py | 44 ++- tools/test_svd2regs.py | 15 + 7 files changed, 195 insertions(+), 297 deletions(-) create mode 100644 catalog/toolchains/gcc-xtensa.json diff --git a/catalog/boards/esp32_devkitc/board.json b/catalog/boards/esp32_devkitc/board.json index d98c1ccf4..8f7c7d7b7 100644 --- a/catalog/boards/esp32_devkitc/board.json +++ b/catalog/boards/esp32_devkitc/board.json @@ -538,5 +538,25 @@ }, "tests": [ "tests/*.json" - ] -} \ No newline at end of file + ], + "i18n": { + "fr": { + "source": "6536a91c8d63", + "description": "L'ESP32-DevKitC V4 d'Espressif avec un module ESP32-WROOM-32E (l'ESP32-D0WD-V3 avec son quartz de 40 MHz et 4 Mo de flash), le régulateur AMS1117-3.3 qui fait 3,3 V à partir des 5 V du micro-USB à travers une Schottky BAT760, la LED rouge d'alimentation, le bouton Boot sur GPIO0, le bouton EN sur CHIP_PU avec son RC, et deux connecteurs de 19 broches. Le pont USB-UART CP2102N sur UART0 (GPIO1/GPIO3) est la console série de la session, et un port série sur l'hôte (embsim serve --serial). Le firmware est une image de flash (bin), telle qu'idf.py et esptool l'écrivent.", + "fidelity": { + "functional": { + "claims": "Les nets, les ports, la LED d'alimentation et les deux boutons comme composants ; l'ESP32 exécute son firmware depuis l'image de flash (voir sa description) : la ROM d'Espressif, le bootloader de second niveau et l'application, comme sur la carte. EN tire CHIP_PU à l'état bas et la puce redémarre avec rst:0x1 (POWERON_RESET) ; Boot maintenu pendant ce temps fait attendre la ROM en mode téléchargement. Le port du CP2102N est la console de la session sur UART0 (GPIO1/GPIO3) et un port série sur l'hôte.", + "omissions": [ + "le CP2102N lui-même : son énumération USB, ses lignes modem et donc le circuit de programmation automatique (Q1, Q2 et R21, R22 qui laissent les DTR et RTS d'esptool piloter EN et GPIO0) : appuyer sur Boot et EN fait ce qu'ils font", + "les diodes ESD des lignes USB (LESD5D5.0CT1G) et le connecteur USB", + "les broches de flash GPIO6-11 sur les connecteurs (D0-D3, CMD, CLK) : le module les garde pour sa flash et les ports les omettent", + "le dessin de la carte et le modèle du banc" + ] + }, + "electrical": { + "claims": "Le chemin d'alimentation du schéma : VBUS à travers la Schottky vers 5 V, l'AMS1117-3.3 vers 3,3 V, la LED, le RC de EN." + } + } + } + } +} diff --git a/catalog/mcus/espressif/esp32/mcu.json b/catalog/mcus/espressif/esp32/mcu.json index 1bdc0f9b9..d28ecda12 100644 --- a/catalog/mcus/espressif/esp32/mcu.json +++ b/catalog/mcus/espressif/esp32/mcu.json @@ -267,7 +267,7 @@ }, "fidelity": { "functional": { - "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), LEDC (eight high-speed and eight low-speed PWM channels on eight timers, fading, on the matrix's ledc_hs/ls_sig_out), I2C0 and I2C1 (master and slave on the open-drain lines through the matrix, the 16 commands with RSTART, WRITE, READ, STOP and END, the bus timing in APB_CLK cycles, ACK checks, arbitration, the SCL timeout, the 32-byte FIFOs through DATA and its AHB view, 7- and 10-bit slave addresses; ESP-IDF's i2c_eeprom runs on a 24LC256), PERIP_RST_EN's resets of I2C0, I2C1 and LEDC, the internal analog I2C master, the random number generator and the peripherals' AHB view.", + "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), LEDC (eight high-speed and eight low-speed PWM channels on eight timers, fading, on the matrix's ledc_hs/ls_sig_out), I2C0 and I2C1 (master and slave on the open-drain lines through the matrix, the 16 commands with RSTART, WRITE, READ, STOP and END, the bus timing in APB_CLK cycles, ACK checks, arbitration, the SCL timeout, the 32-byte FIFOs through DATA and its AHB view, 7- and 10-bit slave addresses; ESP-IDF's i2c_eeprom runs on a 24LC256), PERIP_RST_EN's resets of I2C0, I2C1 and LEDC, the internal analog I2C master, the random number generator and the peripherals' AHB view. The evidence: test_lx6.c's programs assembled by Espressif's assembler (integer, shifts, multiply and divide, loops, branches, memory with S32C1I, windowed recursion through the ROM's own window vectors, SYSCALL, the CCOMPARE timer interrupt, FPU, MAC16) and the APP_CPU's ROM data kept apart; ESP-IDF v5.4's hello_world, blink, ledc_basic and i2c_eeprom built unmodified for the esp32 target on the ESP32-DevKitC V4, from the ROM's banner to esp_restart; the ROM image equals Espressif QEMU's esp32-v3-rom.bin byte for byte, and the APP_CPU's view of it QEMU's esp32-v3-rom-app.bin.", "omissions": [ "Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written)", "the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept", @@ -278,9 +278,12 @@ "the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection" ], "validation": [ - "tests/unit/test_lx6.c: programs assembled by Espressif's assembler (integer, shifts, multiply and divide, loops, branches, memory with S32C1I, windowed recursion through the ROM's own window vectors, SYSCALL, the CCOMPARE timer interrupt, FPU, MAC16), and the APP_CPU's ROM data kept apart", - "tests/vendor/esp-idf: ESP-IDF v5.4's hello_world, blink and ledc_basic built unmodified for the esp32 target on the ESP32-DevKitC V4, from the ROM's banner to esp_restart", - "the ROM image equals Espressif QEMU's esp32-v3-rom.bin byte for byte, and the APP_CPU's view of it QEMU's esp32-v3-rom-app.bin" + "repo:tests/unit/test_lx6.c", + "repo:tests/vendor/esp-idf/run.py", + "repo:tests/vendor/esp-idf/examples/esp32_hello_world/scenario.json", + "repo:tests/vendor/esp-idf/examples/esp32_blink/scenario.json", + "repo:tests/vendor/esp-idf/examples/esp32_ledc_basic/scenario.json", + "repo:tests/vendor/esp-idf/examples/esp32_i2c_eeprom/scenario.json" ] }, "timed": { @@ -383,5 +386,40 @@ ], "packages": { "wroom32e": "wroom32e.json" + }, + "i18n": { + "fr": { + "source": "40e3065d4198", + "description": "Espressif ESP32 (ESP32-D0WD-V3, révision de puce v3.1) : deux cœurs Xtensa LX6 jusqu'à 240 MHz, 520 Kio de SRAM, 448 Kio de ROM, 16 Kio de mémoire RTC, une flash SPI externe derrière les caches des cœurs, Wi-Fi et Bluetooth ; le MCU de l'ESP32-DevKitC. La ROM d'Espressif, le bootloader de second niveau d'ESP-IDF et les applications ESP-IDF tournent sur les deux cœurs ; les radios ne sont pas encore modélisées.", + "fidelity": { + "functional": { + "claims": "Les deux cœurs Xtensa LX6 (mcu/xtensa/lx6.c, lx6fp.c) tels que les configure le core-isa.h d'Espressif : les instructions de base et de densité, les registres fenêtrés (64, le contrôle de débordement de chaque instruction, CALL4/8/12, ENTRY, RETW et son sous-débordement, MOVSP, ROTW, L32E/S32E), les boucles sans surcoût, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, les booléens, S32C1I avec SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), le paquet MAC16, la FPU simple précision (modes d'arrondi et drapeaux IEEE ; les séquences de division et de racine carrée qu'exécute libgcc donnent des résultats correctement arrondis), les exceptions XEA2 avec vecteurs relocalisables, 32 interruptions aux niveaux et types de core-isa.h (niveau, front, logicielles, les temporisateurs CCOMPARE0-2 contre CCOUNT, NMI), RSIL, WAITI, RFI, BREAK, IBREAK, DBREAK et ICOUNT de l'option de débogage, la protection par régions (huit régions de 512 Mo, 15 sans accès), les chargements et écritures non alignés, et les accès de données en 32 bits seulement du bus d'instructions. Les deux cœurs partagent la machine, l'APP_CPU maintenu en reset jusqu'à ce que DPORT le libère et voyant la ROM avec ses pointeurs vers les données ROM du PRO_CPU déplacés vers les siennes, comme dans l'image ROM APP_CPU d'Espressif. Le démarrage : la ROM d'Espressif (esp32_rev300_rom.elf, révision v3) depuis le vecteur de reset, sa bannière, les broches de configuration (GPIO_STRAP : SPI_FAST_FLASH_BOOT, ou le mode téléchargement avec GPIO0 bas), les causes de reset (RTC_CNTL), le contrôle anti-glitch de l'eFuse, la lecture de la flash par SPI1, le bootloader de second niveau d'ESP-IDF et sa vérification d'image sur l'accélérateur SHA, la MMU flash des caches, et l'application sur les deux cœurs. Les modèles : DPORT (reset, horloge et blocage de l'APP_CPU, CPU_PER_CONF, vidage et état des caches, tables de la MMU flash, les 69 sources de la matrice d'interruptions, les interruptions inter-CPU), RTC_CNTL (causes de reset, le temporisateur RTC, le chien de garde RTC avec sa protection au démarrage sur flash, les resets et blocages logiciels, les registres de stockage, SOC_CLK_SEL), APB_CTRL (les horloges CPU et APB depuis XTAL, PLL ou RC_FAST), le contrôleur eFuse avec les bits d'usine de la puce (ESP32-D0WD-V3 v3.1, une MAC avec son CRC, ou celle des réglages de la carte), GPIO et IO_MUX (40 plots, la matrice, tirages, drain ouvert, interruptions sur chaque CPU, le verrou de configuration), UART0-2 (FIFO, trames au débit, interruptions), TIMG0/1 (T0, T1, le temporisateur LAC, le MWDT avec sa protection au démarrage sur flash, la calibration de RTC_SLOW_CLK), SPI0/SPI1 avec la flash Giantec GT25Q32A du module (C4 60 16, l'identifiant que rapporte la carte du propriétaire), l'accélérateur SHA (SHA-1, -256, -384, -512), LEDC (huit canaux MLI rapides et huit lents sur huit temporisateurs, le fondu, sur ledc_hs/ls_sig_out de la matrice), I2C0 et I2C1 (maître et esclave sur les lignes à drain ouvert à travers la matrice, les 16 commandes avec RSTART, WRITE, READ, STOP et END, le timing du bus en cycles d'APB_CLK, les contrôles d'ACK, l'arbitrage, le délai d'attente sur SCL, les FIFO de 32 octets par DATA et sa vue AHB, les adresses esclaves sur 7 et 10 bits ; l'exemple i2c_eeprom d'ESP-IDF fonctionne avec une 24LC256), les resets de PERIP_RST_EN pour I2C0, I2C1 et LEDC, le maître I2C analogique interne, le générateur de nombres aléatoires et la vue AHB des périphériques. Les preuves : les programmes de test_lx6.c assemblés par l'assembleur d'Espressif (entiers, décalages, multiplication et division, boucles, branchements, mémoire avec S32C1I, récursion fenêtrée à travers les vecteurs de fenêtre de la ROM, SYSCALL, l'interruption du temporisateur CCOMPARE, FPU, MAC16) et les données ROM de l'APP_CPU tenues à part ; hello_world, blink, ledc_basic et i2c_eeprom d'ESP-IDF v5.4 compilés sans modification pour la cible esp32 sur l'ESP32-DevKitC V4, de la bannière de la ROM jusqu'à esp_restart ; l'image ROM est identique octet pour octet à l'esp32-v3-rom.bin du QEMU d'Espressif, et la vue qu'en a l'APP_CPU à son esp32-v3-rom-app.bin.", + "omissions": [ + "le Wi-Fi, le Bluetooth et l'étage RF (WIFI_MAC, BB, NRX, FE : leurs registres gardent ce qui y est écrit)", + "les instructions de l'accélérateur double précision (F64ADDC, F64ITER et les autres : leurs encodages sont publics, pas ce qu'elles font ; un programme qui en exécute une s'arrête avec un diagnostic), bien que ses registres soient conservés", + "la PSRAM (DRAM1), le remappage de démarrage, les permissions d'accès APB/AHB, les régions verrouillées et le timing des caches (les pages mappées sont lues entières), les interruptions d'erreur d'accès de la MMU", + "I2C : le mode hors FIFO (NONFIFO_EN, RX_REC_FULL, TX_SEND_EMPTY), SAMPLE_SCL_LEVEL, les filtres d'entrée (seulement leur part du temps haut de SCL), le contrôle R/W de l'adressage sur 10 bits", + "les périphériques sans modèle pour l'instant : I2S0/1, SPI2/3 (HSPI, VSPI), RMT, MCPWM, PCNT, TWAI, SDMMC, EMAC, les ADC SAR, les DAC, le capteur tactile, l'IO MUX RTC et le coprocesseur ULP, les accélérateurs AES et RSA, le chiffrement de la flash", + "les modes de veille (veille légère et profonde, les domaines d'alimentation), la détection de baisse de tension", + "le contrôle du schéma de codage 3/4 de l'eFuse, la protection en écriture par champ du BLOCK0" + ] + }, + "timed": { + "claims": "Timing des instructions estimé : un cycle, trois pour un branchement pris, un saut, un appel ou un retour, deux pour MULL, douze pour une division, trois pour l'entrée d'une exception ; CCOUNT compte ces cycles. Les horloges suivent les registres : CPU_CLK depuis XTAL (40 MHz en sortie de reset), les 80, 160 ou 240 MHz de la PLL, ou RC_FAST ; APB_CLK ; RTC_SLOW_CLK aux 150 kHz nominaux de l'oscillateur RC.", + "omissions": [ + "les verrouillages du pipeline LX6 et le timing du tampon de boucle", + "les défauts de cache et la latence de la flash (le temps de bus des transferts SPI est compté sur SPI1, ainsi que les temps de programmation et d'effacement de la flash)", + "l'erreur et la dérive des oscillateurs RC" + ] + }, + "electrical": { + "claims": "Valeurs par défaut du modèle de broche GPIO à 3,3 V d'après les caractéristiques DC de la fiche technique : VIH 0,75 VDD, VIL 0,25 VDD, VOH 0,8 VDD, VOL 0,1 VDD, tirages de 45 kOhm ; GPIO34-39 en entrée seulement. Le courant d'alimentation (ADR-036, modes d'alimentation de la fiche technique v5.3) : les cœurs actifs et en attente à leur horloge, et maintenus éteints.", + "omissions": [ + "l'effet de la force de sortie des plots", + "le courant propre de chaque périphérique (la fiche technique donne les modes, pas les parties)" + ] + } + } + } } -} \ No newline at end of file +} diff --git a/catalog/mcus/espressif/esp32/registers.json b/catalog/mcus/espressif/esp32/registers.json index 7287b6ae6..eca92b1ef 100644 --- a/catalog/mcus/espressif/esp32/registers.json +++ b/catalog/mcus/espressif/esp32/registers.json @@ -4,7 +4,7 @@ "id": "espressif/esp32/registers", "version": "1.0.0", "device": "ESP32", - "source": "converted from esp32.svd by tools/importers/svd2regs.py; peripherals left out: RNG (the random number generator's one register (RNG_DATA, TRM 26), which the SVD puts at the AHB address 0x60035144; the chip's model answers at 0x3FF75144 and its AHB alias (misc.c))", + "source": "converted from esp32.svd by tools/importers/svd2regs.py; peripherals left out: RNG (the random number generator's one register (RNG_DATA, TRM 26), which the SVD puts at the AHB address 0x60035144; the chip's model answers at 0x3FF75144 and its AHB alias (misc.c)); fields corrected from the reference manual: DPORT.WIFI_CLK_EN.WIFI_CLK_EN:0:32; fields reserved by the reference manual taken out: GPIO.CPUSDIO_INT1.PIN_PAD_DRIVER, GPIO.CPUSDIO_INT1.PIN_INT_TYPE, GPIO.CPUSDIO_INT1.PIN_WAKEUP_ENABLE, GPIO.CPUSDIO_INT1.PIN_CONFIG, GPIO.CPUSDIO_INT1.PIN_INT_ENA, SENS.SAR_MEAS_WAIT2.FORCE_XPD_SAR_SW; overlapping fields taken out: DPORT.PRO_DCACHE_DBUG0 (PRO_CACHE_MMU_IA), DPORT.PRO_DCACHE_DBUG3 (PRO_CPU_DISABLED_CACHE_IA), DPORT.APP_DCACHE_DBUG0 (APP_CACHE_MMU_IA), DPORT.APP_DCACHE_DBUG3 (APP_CPU_DISABLED_CACHE_IA), DPORT.PRO_CPU_RECORD_PDEBUGINST (RECORD_PRO_PDEBUGINST), DPORT.PRO_CPU_RECORD_PDEBUGSTATUS (RECORD_PRO_PDEBUGSTATUS, RECORD_PDEBUGSTATUS_INSNTYPE), DPORT.PRO_CPU_RECORD_PDEBUGDATA (RECORD_PRO_PDEBUGDATA, RECORD_PDEBUGDATA_EXCVEC, RECORD_PDEBUGDATA_INSNTYPE_SR, RECORD_PDEBUGDATA_INSNTYPE_RER, RECORD_PDEBUGDATA_INSNTYPE_WER, RECORD_PDEBUGDATA_INSNTYPE_ER, RECORD_PDEBUGDATA_STALL_IPIF, RECORD_PDEBUGDATA_INSNTYPE_RSR, RECORD_PDEBUGDATA_INSNTYPE_WSR, RECORD_PDEBUGDATA_INSNTYPE_XSR, RECORD_PDEBUGDATA_STALL_BPIFETCH, RECORD_PDEBUGDATA_EXCCAUSE, RECORD_PDEBUGDATA_STALL_BANKCONFL), DPORT.PRO_CPU_RECORD_PDEBUGLS0STAT (RECORD_PRO_PDEBUGLS0STAT), DPORT.CACHE_IA_INT_EN (CACHE_IA_INT_EN), UART0.MEM_RX_STATUS (MEM_RX_STATUS), RTC_CNTL.LOW_POWER_ST (LOW_POWER_DIAG0), RTC_CNTL.BROWN_OUT (RTC_MEM_CRC_ADDR, RTC_MEM_CRC_LEN, RTC_MEM_CRC_FINISH), RMT.CH0STATUS (STATUS), RMT.CH1STATUS (STATUS), RMT.CH2STATUS (STATUS), RMT.CH3STATUS (STATUS), RMT.CH4STATUS (STATUS), RMT.CH5STATUS (STATUS), RMT.CH6STATUS (STATUS), RMT.CH7STATUS (STATUS), PCNT.U0_STATUS (CORE_STATUS_U0), PCNT.U1_STATUS (CORE_STATUS_U0), PCNT.U2_STATUS (CORE_STATUS_U0), PCNT.U3_STATUS (CORE_STATUS_U0), PCNT.U4_STATUS (CORE_STATUS_U0), PCNT.U5_STATUS (CORE_STATUS_U0), PCNT.U6_STATUS (CORE_STATUS_U0), PCNT.U7_STATUS (CORE_STATUS_U0)", "interrupts": { "WIFI_MAC": 0, "WIFI_NMI": 1, @@ -1404,21 +1404,6 @@ "WIFI_CLK_EN", 0, 32 - ], - [ - "WIFI_CLK_WIFI_EN", - 0, - 3 - ], - [ - "WIFI_CLK_WIFI_BT_COMMON", - 0, - 6 - ], - [ - "WIFI_CLK_BT_EN", - 11, - 3 ] ] }, @@ -3887,12 +3872,6 @@ 0, 1 ], - [ - "PRO_CACHE_MMU_IA", - 0, - 1, - "r" - ], [ "PRO_CACHE_IA", 1, @@ -3971,12 +3950,6 @@ 9, "r" ], - [ - "PRO_CPU_DISABLED_CACHE_IA", - 9, - 6, - "r" - ], [ "PRO_CPU_DISABLED_CACHE_IA_OPPOSITE", 9, @@ -4096,12 +4069,6 @@ 0, 1 ], - [ - "APP_CACHE_MMU_IA", - 0, - 1, - "r" - ], [ "APP_CACHE_IA", 1, @@ -4180,12 +4147,6 @@ 9, "r" ], - [ - "APP_CPU_DISABLED_CACHE_IA", - 9, - 6, - "r" - ], [ "APP_CPU_DISABLED_CACHE_IA_OPPOSITE", 9, @@ -4346,12 +4307,6 @@ "name": "PRO_CPU_RECORD_PDEBUGINST", "offset": "0x44C", "fields": [ - [ - "RECORD_PRO_PDEBUGINST", - 0, - 32, - "r" - ], [ "RECORD_PDEBUGINST_SZ", 0, @@ -4383,21 +4338,10 @@ "name": "PRO_CPU_RECORD_PDEBUGSTATUS", "offset": "0x450", "fields": [ - [ - "RECORD_PRO_PDEBUGSTATUS", - 0, - 8, - "r" - ], [ "RECORD_PDEBUGSTATUS_BBCAUSE", 0, 6 - ], - [ - "RECORD_PDEBUGSTATUS_INSNTYPE", - 0, - 6 ] ] }, @@ -4405,52 +4349,21 @@ "name": "PRO_CPU_RECORD_PDEBUGDATA", "offset": "0x454", "fields": [ - [ - "RECORD_PRO_PDEBUGDATA", - 0, - 32, - "r" - ], [ "RECORD_PDEBUGDATA_DEP_OTHER", 0, 1 ], - [ - "RECORD_PDEBUGDATA_EXCVEC", - 0, - 5 - ], - [ - "RECORD_PDEBUGDATA_INSNTYPE_SR", - 0, - 8 - ], - [ - "RECORD_PDEBUGDATA_INSNTYPE_RER", - 0, - 1 - ], [ "RECORD_PDEBUGDATA_STALL_BUFF", 1, 1 ], - [ - "RECORD_PDEBUGDATA_INSNTYPE_WER", - 1, - 1 - ], [ "RECORD_PDEBUGDATA_STALL_BUFFCONFL", 2, 1 ], - [ - "RECORD_PDEBUGDATA_INSNTYPE_ER", - 2, - 12 - ], [ "RECORD_PDEBUGDATA_STALL_DCM", 3, @@ -4476,36 +4389,16 @@ 8, 1 ], - [ - "RECORD_PDEBUGDATA_STALL_IPIF", - 8, - 1 - ], - [ - "RECORD_PDEBUGDATA_INSNTYPE_RSR", - 8, - 1 - ], [ "RECORD_PDEBUGDATA_STALL_TIE", 9, 1 ], - [ - "RECORD_PDEBUGDATA_INSNTYPE_WSR", - 9, - 1 - ], [ "RECORD_PDEBUGDATA_STALL_RUN", 10, 1 ], - [ - "RECORD_PDEBUGDATA_INSNTYPE_XSR", - 10, - 1 - ], [ "RECORD_PDEBUGDATA_DEP_STR", 11, @@ -4516,11 +4409,6 @@ 12, 1 ], - [ - "RECORD_PDEBUGDATA_STALL_BPIFETCH", - 12, - 1 - ], [ "RECORD_PDEBUGDATA_STALL_L32R", 13, @@ -4541,16 +4429,6 @@ 16, 1 ], - [ - "RECORD_PDEBUGDATA_EXCCAUSE", - 16, - 6 - ], - [ - "RECORD_PDEBUGDATA_STALL_BANKCONFL", - 16, - 1 - ], [ "RECORD_PDEBUGDATA_DEP_HALT", 17, @@ -4584,12 +4462,6 @@ "name": "PRO_CPU_RECORD_PDEBUGLS0STAT", "offset": "0x45C", "fields": [ - [ - "RECORD_PRO_PDEBUGLS0STAT", - 0, - 32, - "r" - ], [ "RECORD_PDEBUGLS0STAT_TYPE", 0, @@ -5626,11 +5498,6 @@ "name": "CACHE_IA_INT_EN", "offset": "0x5A0", "fields": [ - [ - "CACHE_IA_INT_EN", - 0, - 28 - ], [ "CACHE_IA_INT_APP_DROM0", 0, @@ -8168,12 +8035,6 @@ "name": "MEM_RX_STATUS", "offset": "0x60", "fields": [ - [ - "MEM_RX_STATUS", - 0, - 24, - "r" - ], [ "MEM_RX_RD_ADDR", 2, @@ -10339,31 +10200,6 @@ 0, 8, "r" - ], - [ - "PIN_PAD_DRIVER", - 2, - 1 - ], - [ - "PIN_INT_TYPE", - 7, - 3 - ], - [ - "PIN_WAKEUP_ENABLE", - 10, - 1 - ], - [ - "PIN_CONFIG", - 11, - 2 - ], - [ - "PIN_INT_ENA", - 13, - 5 ] ] }, @@ -20164,12 +20000,6 @@ "name": "LOW_POWER_ST", "offset": "0xC0", "fields": [ - [ - "LOW_POWER_DIAG0", - 0, - 32, - "r" - ], [ "RDY_FOR_WAKEUP", 19, @@ -20330,11 +20160,6 @@ 8, 1 ], - [ - "RTC_MEM_CRC_ADDR", - 9, - 11 - ], [ "CLOSE_FLASH_ENA", 14, @@ -20350,11 +20175,6 @@ 16, 10 ], - [ - "RTC_MEM_CRC_LEN", - 20, - 11 - ], [ "RST_ENA", 26, @@ -20375,11 +20195,6 @@ 31, 1, "r" - ], - [ - "RTC_MEM_CRC_FINISH", - 31, - 1 ] ] }, @@ -22311,11 +22126,6 @@ "offset": "0x0C", "reset": "0x0020000A", "fields": [ - [ - "FORCE_XPD_SAR_SW", - 0, - 1 - ], [ "SAR_AMP_WAIT3", 0, @@ -33935,12 +33745,6 @@ "name": "CH0STATUS", "offset": "0x60", "fields": [ - [ - "STATUS", - 0, - 32, - "r" - ], [ "MEM_WADDR_EX", 0, @@ -33995,12 +33799,6 @@ "name": "CH1STATUS", "offset": "0x64", "fields": [ - [ - "STATUS", - 0, - 32, - "r" - ], [ "MEM_WADDR_EX", 0, @@ -34055,12 +33853,6 @@ "name": "CH2STATUS", "offset": "0x68", "fields": [ - [ - "STATUS", - 0, - 32, - "r" - ], [ "MEM_WADDR_EX", 0, @@ -34115,12 +33907,6 @@ "name": "CH3STATUS", "offset": "0x6C", "fields": [ - [ - "STATUS", - 0, - 32, - "r" - ], [ "MEM_WADDR_EX", 0, @@ -34175,12 +33961,6 @@ "name": "CH4STATUS", "offset": "0x70", "fields": [ - [ - "STATUS", - 0, - 32, - "r" - ], [ "MEM_WADDR_EX", 0, @@ -34235,12 +34015,6 @@ "name": "CH5STATUS", "offset": "0x74", "fields": [ - [ - "STATUS", - 0, - 32, - "r" - ], [ "MEM_WADDR_EX", 0, @@ -34295,12 +34069,6 @@ "name": "CH6STATUS", "offset": "0x78", "fields": [ - [ - "STATUS", - 0, - 32, - "r" - ], [ "MEM_WADDR_EX", 0, @@ -34355,12 +34123,6 @@ "name": "CH7STATUS", "offset": "0x7C", "fields": [ - [ - "STATUS", - 0, - 32, - "r" - ], [ "MEM_WADDR_EX", 0, @@ -36756,12 +36518,6 @@ "name": "U0_STATUS", "offset": "0x90", "fields": [ - [ - "CORE_STATUS_U0", - 0, - 32, - "r" - ], [ "ZERO_MODE", 0, @@ -36798,12 +36554,6 @@ "name": "U1_STATUS", "offset": "0x94", "fields": [ - [ - "CORE_STATUS_U0", - 0, - 32, - "r" - ], [ "ZERO_MODE", 0, @@ -36840,12 +36590,6 @@ "name": "U2_STATUS", "offset": "0x98", "fields": [ - [ - "CORE_STATUS_U0", - 0, - 32, - "r" - ], [ "ZERO_MODE", 0, @@ -36882,12 +36626,6 @@ "name": "U3_STATUS", "offset": "0x9C", "fields": [ - [ - "CORE_STATUS_U0", - 0, - 32, - "r" - ], [ "ZERO_MODE", 0, @@ -36924,12 +36662,6 @@ "name": "U4_STATUS", "offset": "0xA0", "fields": [ - [ - "CORE_STATUS_U0", - 0, - 32, - "r" - ], [ "ZERO_MODE", 0, @@ -36966,12 +36698,6 @@ "name": "U5_STATUS", "offset": "0xA4", "fields": [ - [ - "CORE_STATUS_U0", - 0, - 32, - "r" - ], [ "ZERO_MODE", 0, @@ -37008,12 +36734,6 @@ "name": "U6_STATUS", "offset": "0xA8", "fields": [ - [ - "CORE_STATUS_U0", - 0, - 32, - "r" - ], [ "ZERO_MODE", 0, @@ -37050,12 +36770,6 @@ "name": "U7_STATUS", "offset": "0xAC", "fields": [ - [ - "CORE_STATUS_U0", - 0, - 32, - "r" - ], [ "ZERO_MODE", 0, diff --git a/catalog/toolchains/gcc-xtensa.json b/catalog/toolchains/gcc-xtensa.json new file mode 100644 index 000000000..ad69dd7f4 --- /dev/null +++ b/catalog/toolchains/gcc-xtensa.json @@ -0,0 +1,69 @@ +{ + "schema_version": 1, + "kind": "toolchain", + "id": "gcc-xtensa", + "name": "Espressif's GNU Xtensa toolchain (xtensa-esp32-elf-gcc)", + "description": "Espressif's crosstool-NG GCC for the ESP32's Xtensa LX6 cores, the one ESP-IDF installs (idf_tools.py, ~/.espressif/tools/xtensa-esp-elf): xtensa-esp32-elf-gcc is the ESP32's configuration (core-isa.h's), with newlib. ESP-IDF's own build (idf.py) makes the flash images the vendor scenarios run; this profile compiles a project's sources into an ELF.", + "detect": { + "command": [ + "xtensa-esp32-elf-gcc", + "--version" + ], + "version_regex": "\\) ([0-9.]+)", + "min_version": "12.2", + "prefix": "xtensa-esp32-elf-" + }, + "targets": { + "esp32": { + "cc": [ + "xtensa-esp32-elf-gcc", + "-mlongcalls" + ], + "link_flags": [ + "-nostartfiles", + "-Wl,--gc-sections", + "--specs=nosys.specs" + ] + } + }, + "steps": { + "compile": [ + "${cc}", + "${cflags}", + "${defines}", + "${include}", + "-g", + "-O2", + "-ffunction-sections", + "-fdata-sections", + "-Wall", + "-c", + "${source}", + "-o", + "${object}" + ], + "link": [ + "${cc}", + "${cflags}", + "${link_flags}", + "-T${linker_script}", + "-Wl,-Map=${map}", + "${objects}", + "-o", + "${image}" + ], + "convert": null + }, + "outputs": { + "image": "elf", + "debug": "dwarf", + "map": true + }, + "environment": { + "PATH_hint": [ + "~/.espressif/tools/xtensa-esp-elf/*/xtensa-esp-elf/bin", + "C:/Espressif/tools/xtensa-esp-elf/*/xtensa-esp-elf/bin", + "/opt/esp/tools/xtensa-esp-elf/*/xtensa-esp-elf/bin" + ] + } +} diff --git a/schemas/mcu.schema.json b/schemas/mcu.schema.json index 2105fc1cb..ca9562da9 100644 --- a/schemas/mcu.schema.json +++ b/schemas/mcu.schema.json @@ -27,7 +27,7 @@ "required": ["backend", "model"], "additionalProperties": false, "properties": { - "backend": {"type": "string", "enum": ["cortex-m", "riscv", "avr", "mips", "pic32", "pic24", "pic16"]}, + "backend": {"type": "string", "enum": ["cortex-m", "riscv", "avr", "mips", "pic32", "pic24", "pic16", "xtensa"]}, "model": {"type": "string", "minLength": 1}, "priority_bits": {"type": "integer", "minimum": 0, "maximum": 8}, "pmp_entries": {"type": "integer", "minimum": 0, "maximum": 64, "description": "A RISC-V core's physical memory protection entries (16 when absent); pmpcfg and pmpaddr past them read 0"}, @@ -209,7 +209,7 @@ "additionalProperties": false, "properties": { "name": {"type": "string", "pattern": "^[A-Z][A-Z0-9]*$"}, - "pins": {"type": "integer", "minimum": 1, "maximum": 32}, + "pins": {"type": "integer", "minimum": 1, "maximum": 64, "description": "Pins in the port: up to 32 for most parts, more where one bank is wider (the ESP32's 40 GPIOs, GPIO_OUT and OUT1)."}, "mask": {"$ref": "common.schema.json#/$defs/hex_or_size", "description": "Bit mask of implemented pins when not contiguous from 0."} } } diff --git a/tools/importers/svd2regs.py b/tools/importers/svd2regs.py index 24c437dac..1585941c2 100644 --- a/tools/importers/svd2regs.py +++ b/tools/importers/svd2regs.py @@ -40,7 +40,15 @@ (repeatable) a memory inside a peripheral the SVD leaves out, as WORDS 32-bit registers NAME0, NAME1, ... (the ESP32-C3's - RMT RAM); recorded likewise""" + RMT RAM); recorded likewise + --resolve-overlaps fields that share bits, which some SVDs + list as alternative views of one register + (Espressif's: a whole-register STATUS + beside its parts): a field containing + another is taken out, and of fields over + the same bits the first is kept; recorded + likewise (after --reserve, so a wrong + field can be taken out first)""" import argparse import json import re @@ -308,6 +316,36 @@ def apply_reserved(doc: dict, specs: list[str]) -> None: doc["source"] += "; fields reserved by the reference manual taken out: " + ", ".join(done) +def resolve_overlaps(doc: dict) -> None: + """In each register, fields that contain another taken out, and of + fields over the same bits all but the first.""" + done = [] + for p in doc["peripherals"]: + for r in p.get("registers", []): + fields = r.get("fields", []) + masks = [((1 << f[2]) - 1) << f[1] for f in fields] + out = set() + for i, mi in enumerate(masks): + for j, mj in enumerate(masks): + if i == j or j in out: + continue + if mi == mj and j < i: + out.add(i) # the same bits: the first kept + elif mi != mj and mi & mj == mj: + out.add(i) # i contains j + kept = [f for k, f in enumerate(fields) if k not in out] + masks = [((1 << f[2]) - 1) << f[1] for f in kept] + if any(masks[a] & masks[b] for a in range(len(masks)) for b in range(a + 1, len(masks))): + raise SystemExit(f"--resolve-overlaps: {p['name']}.{r['name']}: fields still overlap; " + "take the wrong one out with --reserve") + if not out: + continue + r["fields"] = kept + done.append(f"{p['name']}.{r['name']} ({', '.join(fields[k][0] for k in sorted(out))})") + if done: + doc["source"] += "; overlapping fields taken out: " + ", ".join(done) + + def apply_merges(doc: dict, specs: list[str]) -> None: """The one-bit fields NAME0..NAMEn, contiguous, joined into NAME.""" done = [] @@ -412,6 +450,8 @@ def main(argv: Optional[list[str]] = None) -> int: help="one-bit fields NAME0..NAMEn joined into NAME") ap.add_argument("--reserve", action="append", default=[], metavar="PERIPH.REG.NAME", help="a field whose bits the reference manual reserves, taken out") + ap.add_argument("--resolve-overlaps", action="store_true", + help="fields containing another, or repeating its bits, taken out") args = ap.parse_args(argv) doc = convert(args.svd, args.id or (args.svd.stem.lower() + "/registers")) apply_drops(doc, args.drop) @@ -421,6 +461,8 @@ def main(argv: Optional[list[str]] = None) -> int: apply_registers(doc, args.register) apply_fields(doc, args.field) apply_reserved(doc, args.reserve) + if args.resolve_overlaps: + resolve_overlaps(doc) apply_resets(doc, args.reset) apply_interrupts(doc, args.interrupts) args.output.write_text(json.dumps(doc, indent=1) + "\n", encoding="utf-8") diff --git a/tools/test_svd2regs.py b/tools/test_svd2regs.py index 7c78471f7..bc9145c98 100644 --- a/tools/test_svd2regs.py +++ b/tools/test_svd2regs.py @@ -111,6 +111,21 @@ def test_reserved_field(self): with self.assertRaises(SystemExit): svd2regs.apply_reserved(doc, ["TIM3.CNT.CNT_H"]) + def test_resolve_overlaps(self): + def doc_of(fields): + return {"source": "s", "interrupts": {}, "peripherals": [{"name": "PCNT", "base": "0x0", "registers": [ + {"name": "U0_STATUS", "offset": "0x90", "fields": fields}]}]} + # a whole-register view beside its parts, and two names for one bit + doc = doc_of([["CORE_STATUS_U0", 0, 32, "r"], ["ZERO_MODE", 0, 2, "r"], ["THRES1", 2, 1, "r"], + ["DET", 31, 1, "r"], ["FINISH", 31, 1, "r"]]) + svd2regs.resolve_overlaps(doc) + self.assertEqual([f[0] for f in doc["peripherals"][0]["registers"][0]["fields"]], + ["ZERO_MODE", "THRES1", "DET"]) + self.assertIn("PCNT.U0_STATUS (CORE_STATUS_U0, FINISH)", doc["source"]) + # a partial overlap is left to --reserve + with self.assertRaises(SystemExit): + svd2regs.resolve_overlaps(doc_of([["A", 0, 4], ["B", 2, 4]])) + def test_added_register(self): doc = {"source": "s", "interrupts": {}, "peripherals": [{"name": "SYSCFG", "base": "0x0", "registers": [ {"name": "CMPCR", "offset": "0x20", "fields": []}]}]} From 7585110e56c79f25fea7189b07e1ea67bae06308 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Sun, 8 Nov 2026 04:26:30 +0000 Subject: [PATCH 50/57] Gallery: the ESP32-DevKitC running ESP-IDF's i2c_eeprom --- docs/gallery/README.md | 9 +++++++++ docs/gallery/esp32-devkitc/design.png | Bin 0 -> 498099 bytes docs/gallery/esp32-devkitc/run.png | Bin 0 -> 469357 bytes 3 files changed, 9 insertions(+) create mode 100644 docs/gallery/esp32-devkitc/design.png create mode 100644 docs/gallery/esp32-devkitc/run.png diff --git a/docs/gallery/README.md b/docs/gallery/README.md index 4b9fd97db..115e2093f 100644 --- a/docs/gallery/README.md +++ b/docs/gallery/README.md @@ -5,6 +5,15 @@ evolves. Newer milestones come first. The milestone sets from M1 on are captured `ui/apps/workbench/scripts/screenshots.mjs` (the same views each time: `pnpm build`, then `node scripts/screenshots.mjs --out ../../../docs/gallery/`). +## The ESP32-DevKitC running ESP-IDF's i2c_eeprom (2026-11-08) + +The original ESP32 on the machine framework: ESP-IDF v5.4's i2c_eeprom, +built unmodified, writing a 24LC256 through I2C0 at 400 kHz and reading it +back (the part's write cycles counted under the meters). + +| ![Design](esp32-devkitc/design.png) | ![Run](esp32-devkitc/run.png) | +|---|---| + ## The library by category and sub-category (2026-11-03) The real catalog's 205 parts filed as an EDA's device picker files them; diff --git a/docs/gallery/esp32-devkitc/design.png b/docs/gallery/esp32-devkitc/design.png new file mode 100644 index 0000000000000000000000000000000000000000..0dddb9b51ff52aea0b3e7a1c82267d44e121e7cc GIT binary patch literal 498099 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 z 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 z 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 LO$ITXWm{>tM;P7p;c9SP)rot&Wv95 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 I4W~!{1&q9U@Bjb+ literal 0 HcmV?d00001 diff --git a/docs/gallery/esp32-devkitc/run.png b/docs/gallery/esp32-devkitc/run.png new file mode 100644 index 0000000000000000000000000000000000000000..c7f895dd48d98696832cdfae81e8cc52308d4d49 GIT binary patch literal 469357 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 +*`jMf~a@ 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�RMs-%8_MJaLO5`* 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 Date: Sun, 8 Nov 2026 04:43:21 +0000 Subject: [PATCH 51/57] ESP32: gptimer, uart_echo and real_time_stats; MEM_RX_STATUS Three more of ESP-IDF's examples run unmodified on the DevKitC: gptimer (TIMG0, 17/17), uart_echo on UART2 (6/6) and real_time_stats across both cores (10/10, with IDLE0 and IDLE1). uart_echo echoed 128 bytes for each line: on the ESP32, ESP-IDF's uart_ll_get_rxfifo_len counts the bytes waiting from MEM_RX_STATUS's read and write addresses (the DPORT read's FIFO count is not to be trusted), and with both at 0 takes a nonzero count for a full FIFO. The UART model now moves the two addresses with each byte. --- CHANGELOG.md | 4 +- catalog/mcus/espressif/esp32/mcu.json | 11 +- core/mcu/esp32/soc/uart.c | 16 +- docs/STATUS.md | 5 +- .../examples/esp32_gptimer/project.json | 61 ++++++++ .../examples/esp32_gptimer/scenario.json | 146 ++++++++++++++++++ .../esp32_real_time_stats/project.json | 61 ++++++++ .../esp32_real_time_stats/scenario.json | 90 +++++++++++ .../examples/esp32_uart_echo/project.json | 91 +++++++++++ .../examples/esp32_uart_echo/scenario.json | 71 +++++++++ tests/vendor/esp-idf/run.py | 6 + 11 files changed, 553 insertions(+), 9 deletions(-) create mode 100644 tests/vendor/esp-idf/examples/esp32_gptimer/project.json create mode 100644 tests/vendor/esp-idf/examples/esp32_gptimer/scenario.json create mode 100644 tests/vendor/esp-idf/examples/esp32_real_time_stats/project.json create mode 100644 tests/vendor/esp-idf/examples/esp32_real_time_stats/scenario.json create mode 100644 tests/vendor/esp-idf/examples/esp32_uart_echo/project.json create mode 100644 tests/vendor/esp-idf/examples/esp32_uart_echo/scenario.json diff --git a/CHANGELOG.md b/CHANGELOG.md index b94e50c75..0e3f4da43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,9 @@ versions follow semantic versioning. There is no release yet. - Espressif's ROM, ESP-IDF v5.4's bootloader and hello_world run unmodified through `esp_restart` (`tests/vendor/esp-idf`: `esp32_hello_world` 9/9, `esp32_blink` 6/6, `esp32_ledc_basic` 2/2, `esp32_i2c_eeprom` 3/3 on a - 24LC256); `test_lx6` checks the core with programs from Espressif's assembler. + 24LC256, `esp32_gptimer` 17/17, `esp32_uart_echo` 6/6 on UART2, + `esp32_real_time_stats` 10/10 across both cores; the UARTs keep + MEM_RX_STATUS's addresses, from which ESP-IDF counts the bytes waiting); `test_lx6` checks the core with programs from Espressif's assembler. ### Original ESP32 digital per-pin hold diff --git a/catalog/mcus/espressif/esp32/mcu.json b/catalog/mcus/espressif/esp32/mcu.json index d28ecda12..f8e536095 100644 --- a/catalog/mcus/espressif/esp32/mcu.json +++ b/catalog/mcus/espressif/esp32/mcu.json @@ -267,7 +267,7 @@ }, "fidelity": { "functional": { - "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), LEDC (eight high-speed and eight low-speed PWM channels on eight timers, fading, on the matrix's ledc_hs/ls_sig_out), I2C0 and I2C1 (master and slave on the open-drain lines through the matrix, the 16 commands with RSTART, WRITE, READ, STOP and END, the bus timing in APB_CLK cycles, ACK checks, arbitration, the SCL timeout, the 32-byte FIFOs through DATA and its AHB view, 7- and 10-bit slave addresses; ESP-IDF's i2c_eeprom runs on a 24LC256), PERIP_RST_EN's resets of I2C0, I2C1 and LEDC, the internal analog I2C master, the random number generator and the peripherals' AHB view. The evidence: test_lx6.c's programs assembled by Espressif's assembler (integer, shifts, multiply and divide, loops, branches, memory with S32C1I, windowed recursion through the ROM's own window vectors, SYSCALL, the CCOMPARE timer interrupt, FPU, MAC16) and the APP_CPU's ROM data kept apart; ESP-IDF v5.4's hello_world, blink, ledc_basic and i2c_eeprom built unmodified for the esp32 target on the ESP32-DevKitC V4, from the ROM's banner to esp_restart; the ROM image equals Espressif QEMU's esp32-v3-rom.bin byte for byte, and the APP_CPU's view of it QEMU's esp32-v3-rom-app.bin.", + "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts, MEM_RX_STATUS's addresses that ESP-IDF counts the bytes waiting from), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), LEDC (eight high-speed and eight low-speed PWM channels on eight timers, fading, on the matrix's ledc_hs/ls_sig_out), I2C0 and I2C1 (master and slave on the open-drain lines through the matrix, the 16 commands with RSTART, WRITE, READ, STOP and END, the bus timing in APB_CLK cycles, ACK checks, arbitration, the SCL timeout, the 32-byte FIFOs through DATA and its AHB view, 7- and 10-bit slave addresses; ESP-IDF's i2c_eeprom runs on a 24LC256), PERIP_RST_EN's resets of I2C0, I2C1 and LEDC, the internal analog I2C master, the random number generator and the peripherals' AHB view. The evidence: test_lx6.c's programs assembled by Espressif's assembler (integer, shifts, multiply and divide, loops, branches, memory with S32C1I, windowed recursion through the ROM's own window vectors, SYSCALL, the CCOMPARE timer interrupt, FPU, MAC16) and the APP_CPU's ROM data kept apart; ESP-IDF v5.4's hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo and real_time_stats built unmodified for the esp32 target on the ESP32-DevKitC V4, from the ROM's banner to esp_restart; the ROM image equals Espressif QEMU's esp32-v3-rom.bin byte for byte, and the APP_CPU's view of it QEMU's esp32-v3-rom-app.bin.", "omissions": [ "Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written)", "the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept", @@ -283,7 +283,10 @@ "repo:tests/vendor/esp-idf/examples/esp32_hello_world/scenario.json", "repo:tests/vendor/esp-idf/examples/esp32_blink/scenario.json", "repo:tests/vendor/esp-idf/examples/esp32_ledc_basic/scenario.json", - "repo:tests/vendor/esp-idf/examples/esp32_i2c_eeprom/scenario.json" + "repo:tests/vendor/esp-idf/examples/esp32_i2c_eeprom/scenario.json", + "repo:tests/vendor/esp-idf/examples/esp32_gptimer/scenario.json", + "repo:tests/vendor/esp-idf/examples/esp32_uart_echo/scenario.json", + "repo:tests/vendor/esp-idf/examples/esp32_real_time_stats/scenario.json" ] }, "timed": { @@ -389,11 +392,11 @@ }, "i18n": { "fr": { - "source": "40e3065d4198", + "source": "0ba7d2d0d3b6", "description": "Espressif ESP32 (ESP32-D0WD-V3, révision de puce v3.1) : deux cœurs Xtensa LX6 jusqu'à 240 MHz, 520 Kio de SRAM, 448 Kio de ROM, 16 Kio de mémoire RTC, une flash SPI externe derrière les caches des cœurs, Wi-Fi et Bluetooth ; le MCU de l'ESP32-DevKitC. La ROM d'Espressif, le bootloader de second niveau d'ESP-IDF et les applications ESP-IDF tournent sur les deux cœurs ; les radios ne sont pas encore modélisées.", "fidelity": { "functional": { - "claims": "Les deux cœurs Xtensa LX6 (mcu/xtensa/lx6.c, lx6fp.c) tels que les configure le core-isa.h d'Espressif : les instructions de base et de densité, les registres fenêtrés (64, le contrôle de débordement de chaque instruction, CALL4/8/12, ENTRY, RETW et son sous-débordement, MOVSP, ROTW, L32E/S32E), les boucles sans surcoût, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, les booléens, S32C1I avec SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), le paquet MAC16, la FPU simple précision (modes d'arrondi et drapeaux IEEE ; les séquences de division et de racine carrée qu'exécute libgcc donnent des résultats correctement arrondis), les exceptions XEA2 avec vecteurs relocalisables, 32 interruptions aux niveaux et types de core-isa.h (niveau, front, logicielles, les temporisateurs CCOMPARE0-2 contre CCOUNT, NMI), RSIL, WAITI, RFI, BREAK, IBREAK, DBREAK et ICOUNT de l'option de débogage, la protection par régions (huit régions de 512 Mo, 15 sans accès), les chargements et écritures non alignés, et les accès de données en 32 bits seulement du bus d'instructions. Les deux cœurs partagent la machine, l'APP_CPU maintenu en reset jusqu'à ce que DPORT le libère et voyant la ROM avec ses pointeurs vers les données ROM du PRO_CPU déplacés vers les siennes, comme dans l'image ROM APP_CPU d'Espressif. Le démarrage : la ROM d'Espressif (esp32_rev300_rom.elf, révision v3) depuis le vecteur de reset, sa bannière, les broches de configuration (GPIO_STRAP : SPI_FAST_FLASH_BOOT, ou le mode téléchargement avec GPIO0 bas), les causes de reset (RTC_CNTL), le contrôle anti-glitch de l'eFuse, la lecture de la flash par SPI1, le bootloader de second niveau d'ESP-IDF et sa vérification d'image sur l'accélérateur SHA, la MMU flash des caches, et l'application sur les deux cœurs. Les modèles : DPORT (reset, horloge et blocage de l'APP_CPU, CPU_PER_CONF, vidage et état des caches, tables de la MMU flash, les 69 sources de la matrice d'interruptions, les interruptions inter-CPU), RTC_CNTL (causes de reset, le temporisateur RTC, le chien de garde RTC avec sa protection au démarrage sur flash, les resets et blocages logiciels, les registres de stockage, SOC_CLK_SEL), APB_CTRL (les horloges CPU et APB depuis XTAL, PLL ou RC_FAST), le contrôleur eFuse avec les bits d'usine de la puce (ESP32-D0WD-V3 v3.1, une MAC avec son CRC, ou celle des réglages de la carte), GPIO et IO_MUX (40 plots, la matrice, tirages, drain ouvert, interruptions sur chaque CPU, le verrou de configuration), UART0-2 (FIFO, trames au débit, interruptions), TIMG0/1 (T0, T1, le temporisateur LAC, le MWDT avec sa protection au démarrage sur flash, la calibration de RTC_SLOW_CLK), SPI0/SPI1 avec la flash Giantec GT25Q32A du module (C4 60 16, l'identifiant que rapporte la carte du propriétaire), l'accélérateur SHA (SHA-1, -256, -384, -512), LEDC (huit canaux MLI rapides et huit lents sur huit temporisateurs, le fondu, sur ledc_hs/ls_sig_out de la matrice), I2C0 et I2C1 (maître et esclave sur les lignes à drain ouvert à travers la matrice, les 16 commandes avec RSTART, WRITE, READ, STOP et END, le timing du bus en cycles d'APB_CLK, les contrôles d'ACK, l'arbitrage, le délai d'attente sur SCL, les FIFO de 32 octets par DATA et sa vue AHB, les adresses esclaves sur 7 et 10 bits ; l'exemple i2c_eeprom d'ESP-IDF fonctionne avec une 24LC256), les resets de PERIP_RST_EN pour I2C0, I2C1 et LEDC, le maître I2C analogique interne, le générateur de nombres aléatoires et la vue AHB des périphériques. Les preuves : les programmes de test_lx6.c assemblés par l'assembleur d'Espressif (entiers, décalages, multiplication et division, boucles, branchements, mémoire avec S32C1I, récursion fenêtrée à travers les vecteurs de fenêtre de la ROM, SYSCALL, l'interruption du temporisateur CCOMPARE, FPU, MAC16) et les données ROM de l'APP_CPU tenues à part ; hello_world, blink, ledc_basic et i2c_eeprom d'ESP-IDF v5.4 compilés sans modification pour la cible esp32 sur l'ESP32-DevKitC V4, de la bannière de la ROM jusqu'à esp_restart ; l'image ROM est identique octet pour octet à l'esp32-v3-rom.bin du QEMU d'Espressif, et la vue qu'en a l'APP_CPU à son esp32-v3-rom-app.bin.", + "claims": "Les deux cœurs Xtensa LX6 (mcu/xtensa/lx6.c, lx6fp.c) tels que les configure le core-isa.h d'Espressif : les instructions de base et de densité, les registres fenêtrés (64, le contrôle de débordement de chaque instruction, CALL4/8/12, ENTRY, RETW et son sous-débordement, MOVSP, ROTW, L32E/S32E), les boucles sans surcoût, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, les booléens, S32C1I avec SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), le paquet MAC16, la FPU simple précision (modes d'arrondi et drapeaux IEEE ; les séquences de division et de racine carrée qu'exécute libgcc donnent des résultats correctement arrondis), les exceptions XEA2 avec vecteurs relocalisables, 32 interruptions aux niveaux et types de core-isa.h (niveau, front, logicielles, les temporisateurs CCOMPARE0-2 contre CCOUNT, NMI), RSIL, WAITI, RFI, BREAK, IBREAK, DBREAK et ICOUNT de l'option de débogage, la protection par régions (huit régions de 512 Mo, 15 sans accès), les chargements et écritures non alignés, et les accès de données en 32 bits seulement du bus d'instructions. Les deux cœurs partagent la machine, l'APP_CPU maintenu en reset jusqu'à ce que DPORT le libère et voyant la ROM avec ses pointeurs vers les données ROM du PRO_CPU déplacés vers les siennes, comme dans l'image ROM APP_CPU d'Espressif. Le démarrage : la ROM d'Espressif (esp32_rev300_rom.elf, révision v3) depuis le vecteur de reset, sa bannière, les broches de configuration (GPIO_STRAP : SPI_FAST_FLASH_BOOT, ou le mode téléchargement avec GPIO0 bas), les causes de reset (RTC_CNTL), le contrôle anti-glitch de l'eFuse, la lecture de la flash par SPI1, le bootloader de second niveau d'ESP-IDF et sa vérification d'image sur l'accélérateur SHA, la MMU flash des caches, et l'application sur les deux cœurs. Les modèles : DPORT (reset, horloge et blocage de l'APP_CPU, CPU_PER_CONF, vidage et état des caches, tables de la MMU flash, les 69 sources de la matrice d'interruptions, les interruptions inter-CPU), RTC_CNTL (causes de reset, le temporisateur RTC, le chien de garde RTC avec sa protection au démarrage sur flash, les resets et blocages logiciels, les registres de stockage, SOC_CLK_SEL), APB_CTRL (les horloges CPU et APB depuis XTAL, PLL ou RC_FAST), le contrôleur eFuse avec les bits d'usine de la puce (ESP32-D0WD-V3 v3.1, une MAC avec son CRC, ou celle des réglages de la carte), GPIO et IO_MUX (40 plots, la matrice, tirages, drain ouvert, interruptions sur chaque CPU, le verrou de configuration), UART0-2 (FIFO, trames au débit, interruptions, les adresses de MEM_RX_STATUS d'après lesquelles ESP-IDF compte les octets en attente), TIMG0/1 (T0, T1, le temporisateur LAC, le MWDT avec sa protection au démarrage sur flash, la calibration de RTC_SLOW_CLK), SPI0/SPI1 avec la flash Giantec GT25Q32A du module (C4 60 16, l'identifiant que rapporte la carte du propriétaire), l'accélérateur SHA (SHA-1, -256, -384, -512), LEDC (huit canaux MLI rapides et huit lents sur huit temporisateurs, le fondu, sur ledc_hs/ls_sig_out de la matrice), I2C0 et I2C1 (maître et esclave sur les lignes à drain ouvert à travers la matrice, les 16 commandes avec RSTART, WRITE, READ, STOP et END, le timing du bus en cycles d'APB_CLK, les contrôles d'ACK, l'arbitrage, le délai d'attente sur SCL, les FIFO de 32 octets par DATA et sa vue AHB, les adresses esclaves sur 7 et 10 bits ; l'exemple i2c_eeprom d'ESP-IDF fonctionne avec une 24LC256), les resets de PERIP_RST_EN pour I2C0, I2C1 et LEDC, le maître I2C analogique interne, le générateur de nombres aléatoires et la vue AHB des périphériques. Les preuves : les programmes de test_lx6.c assemblés par l'assembleur d'Espressif (entiers, décalages, multiplication et division, boucles, branchements, mémoire avec S32C1I, récursion fenêtrée à travers les vecteurs de fenêtre de la ROM, SYSCALL, l'interruption du temporisateur CCOMPARE, FPU, MAC16) et les données ROM de l'APP_CPU tenues à part ; hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo et real_time_stats d'ESP-IDF v5.4 compilés sans modification pour la cible esp32 sur l'ESP32-DevKitC V4, de la bannière de la ROM jusqu'à esp_restart ; l'image ROM est identique octet pour octet à l'esp32-v3-rom.bin du QEMU d'Espressif, et la vue qu'en a l'APP_CPU à son esp32-v3-rom-app.bin.", "omissions": [ "le Wi-Fi, le Bluetooth et l'étage RF (WIFI_MAC, BB, NRX, FE : leurs registres gardent ce qui y est écrit)", "les instructions de l'accélérateur double précision (F64ADDC, F64ITER et les autres : leurs encodages sont publics, pas ce qu'elles font ; un programme qui en exécute une s'arrête avec un diagnostic), bien que ses registres soient conservés", diff --git a/core/mcu/esp32/soc/uart.c b/core/mcu/esp32/soc/uart.c index 349132885..23eb0ab14 100644 --- a/core/mcu/esp32/soc/uart.c +++ b/core/mcu/esp32/soc/uart.c @@ -29,7 +29,11 @@ * is INT_RAW and INT_ENA, and raises the matrix source (UART0 34, * UART1 35, UART2 36) while nonzero; * - STATUS reads the FIFOs' counts (with MEM_CNT_STATUS's high bits) - * and the lines; RXFIFO_RST and TXFIFO_RST empty the FIFOs while set. + * and the lines; MEM_RX_STATUS the RX RAM's read and write addresses + * (RD_ADDR, WR_ADDR), from which ESP-IDF's uart_ll counts the bytes + * waiting on the ESP32, each moving on a byte and wrapping at the RX + * FIFO's size (counted from the FIFO's start: the RAM's absolute + * addresses are not modelled); RXFIFO_RST and TXFIFO_RST empty the FIFOs while set. * Not modelled: autobaud, IrDA, RS485, the AT_CMD character, software * flow control (XON/XOFF), the RX glitch filter, light-sleep wake-up, * UHCI, the FIFO RAM's sharing between the UARTs beyond its sizes. */ @@ -63,10 +67,11 @@ struct e32_uart { struct esk_mach *s; struct esk_rf_periph *p; struct esk_rf_reg *fifo, *raw, *st, *ena, *clr, *clkdiv, *status, *conf0, *conf1, *idle, *mem, - *memcnt; + *memcnt, *memrx; int unit, source, level; u8 tx[4 * BLOCK], rx[2 * BLOCK]; int ntx, nrx; + u32 rx_rd, rx_wr; /* the RX RAM's read and write addresses (MEM_RX_STATUS) */ int busy; /* a frame, a break or the idle gap is on the TX line */ int clocked; /* the device's clock runs (esk_rf_clock) */ int phase; /* 0 a frame, 1 the idle after it, 2 a break */ @@ -162,6 +167,8 @@ static void update(struct e32_uart *u) ((u32)u->ntx & 0xffu) << 16); if (u->memcnt) esk_rf_hw(u->memcnt, (u32)(u->nrx >> 8) | (u32)(u->ntx >> 8) << 3); + if (u->memrx) + esk_rf_hw(u->memrx, (u->rx_rd & 0x7ffu) << 2 | (u->rx_wr & 0x7ffu) << 13); if ((reg(u->st) != 0) != u->level) { u->level = reg(u->st) != 0; esk_esp32_source(u->s, u->source, u->level); @@ -245,6 +252,7 @@ static void receive(struct e32_uart *u, u32 bits, int framing_error, int brk) set_raw(u, RAW_FRM_ERR); if (u->nrx < rx_cap(u)) { u->rx[u->nrx++] = (u8)(bits & ((1u << n) - 1u)); + u->rx_wr = (u->rx_wr + 1u) % (u32)rx_cap(u); u->rx_any = 1; } else { set_raw(u, RAW_RXFIFO_OVF); @@ -300,6 +308,7 @@ static u32 ur_read(void *ctx, u32 off, int n) if (!u->s->bus.debug) { memmove(u->rx, u->rx + 1, (size_t)(u->nrx - 1)); u->nrx--; + u->rx_rd = (u->rx_rd + 1u) % (u32)rx_cap(u); if (!u->nrx) u->rx_any = 0; update(u); @@ -327,6 +336,7 @@ static void ur_write(void *ctx, u32 off, int n, u32 v) u->ntx = 0; if (reg(u->conf0) & CONF0_RXFIFO_RST) { u->nrx = 0; + u->rx_rd = u->rx_wr = 0; u->rx_any = 0; } if ((reg(u->conf0) & CONF0_TXD_BRK) && !(was0 & CONF0_TXD_BRK)) @@ -403,6 +413,7 @@ static void ur_reset(void *ctx) { struct e32_uart *u = (struct e32_uart *)ctx; u->ntx = u->nrx = 0; + u->rx_rd = u->rx_wr = 0; u->busy = 0; u->clocked = 0; u->left = 0; @@ -443,6 +454,7 @@ void *esk_esp32_uart_create(struct esk_mach *s, struct esk_rf_periph *p) c->idle = esk_rf_reg(p, "IDLE_CONF"); c->mem = esk_rf_reg(p, "MEM_CONF"); c->memcnt = esk_rf_reg(p, "MEM_CNT_STATUS"); + c->memrx = esk_rf_reg(p, "MEM_RX_STATUS"); if (!c->fifo || !c->raw || !c->st || !c->ena || !c->clr || !c->clkdiv || !c->status || !c->conf0 || !c->conf1 || !c->idle || !c->mem) return 0; diff --git a/docs/STATUS.md b/docs/STATUS.md index 511f80037..37798483c 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -15,8 +15,9 @@ hello_world running unmodified on both cores at 160 MHz to `Hello world!` and through `esp_restart` (rst:0xc SW_CPU_RESET). Evidence: `test_lx6` (4,332 checks), the board's three fixtures, and the ESP-IDF vendor scenarios `esp32_hello_world` (9/9), `esp32_blink` (6/6), -`esp32_ledc_basic` (2/2) and `esp32_i2c_eeprom` (3/3, I2C0 writing and -reading a 24LC256 at 400 kHz). Not yet: Wi-Fi and Bluetooth, the +`esp32_ledc_basic` (2/2), `esp32_i2c_eeprom` (3/3, I2C0 writing and +reading a 24LC256 at 400 kHz), `esp32_gptimer` (17/17), `esp32_uart_echo` +(6/6) and `esp32_real_time_stats` (10/10). Not yet: Wi-Fi and Bluetooth, the double-precision accelerator's instructions, the peripherals the description's fidelity lists (I2S, SPI2/3, RMT, ADC, ...), sleep. The standalone harness below remains as its own qualification diff --git a/tests/vendor/esp-idf/examples/esp32_gptimer/project.json b/tests/vendor/esp-idf/examples/esp32_gptimer/project.json new file mode 100644 index 000000000..d30d5c047 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_gptimer/project.json @@ -0,0 +1,61 @@ +{ + "schema_version": 1, + "kind": "project", + "name": "esp-idf-esp32_gptimer", + "description": "ESP-IDF's peripherals/timer_group/gptimer on the ESP32-DevKitC V4, plugged into USB; the console is UART0 through the CP2102N.", + "policy": "electrical", + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc", + "layout": { + "x": 360, + "y": 120 + }, + "firmware": { + "image": "flash.bin", + "format": "bin" + } + } + ], + "components": [ + { + "id": "USB", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5 + }, + "layout": { + "x": 120, + "y": 160 + } + }, + { + "id": "GND1", + "component": "emblink/gnd", + "layout": { + "x": 120, + "y": 320 + } + } + ], + "nets": { + "GND": [ + "USB.-", + "GND1.GND", + "B1.GND1" + ], + "USB5V": [ + "USB.+", + "B1.VBUS" + ] + }, + "probes": [ + { + "id": "console", + "target": "serial:B1", + "kind": "serial" + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/examples/esp32_gptimer/scenario.json b/tests/vendor/esp-idf/examples/esp32_gptimer/scenario.json new file mode 100644 index 000000000..fba0039f7 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_gptimer/scenario.json @@ -0,0 +1,146 @@ +{ + "schema_version": 1, + "kind": "scenario", + "name": "esp32_gptimer", + "until": "9600ms", + "description": "gptimer: TIMG0's T0 at 1 MHz (APB_CLK / 80). Stopped at its 1 s alarm, the count the ISR reads is 1000001 (about 1 us from the alarm to the read at 160 MHz; ESP-IDF's README shows 1000002 on its chip); set to 100 and read back; four auto-reloads a second apart, each read 1 after its reload to 0; then four alarms moved 1 s on each time. Each event is pinned by a window (absent before, present after) rather than by the log's timestamps, which move by a millisecond with the image's size (the build's paths and compile time are in it).", + "inputs": [], + "assertions": [ + { + "id": "not-before-alarm", + "at": "1200ms", + "probe": "console", + "expect": { + "not_contains": "Timer stopped" + } + }, + { + "id": "stopped", + "at": "1300ms", + "probe": "console", + "expect": { + "contains": "example: Timer stopped, count=1000001\r\n" + } + }, + { + "id": "set", + "at": "1300ms", + "probe": "console", + "expect": { + "contains": "example: Timer count value=100\r\n" + } + }, + { + "id": "reload-not-early", + "at": "2200ms", + "probe": "console", + "expect": { + "not_contains": "Timer reloaded" + } + }, + { + "id": "reloaded", + "at": "2300ms", + "probe": "console", + "expect": { + "contains": "example: Timer reloaded, count=1\r\n" + } + }, + { + "id": "reloading", + "at": "5200ms", + "probe": "console", + "expect": { + "not_contains": "example: Stop timer" + } + }, + { + "id": "reloads-done", + "at": "5300ms", + "probe": "console", + "expect": { + "contains": "example: Stop timer" + } + }, + { + "id": "alarm1-not-early", + "at": "6200ms", + "probe": "console", + "expect": { + "not_contains": "example: Timer alarmed, count=1000001" + } + }, + { + "id": "alarm1", + "at": "6300ms", + "probe": "console", + "expect": { + "contains": "example: Timer alarmed, count=1000001\r\n" + } + }, + { + "id": "alarm2-not-early", + "at": "7200ms", + "probe": "console", + "expect": { + "not_contains": "example: Timer alarmed, count=2000001" + } + }, + { + "id": "alarm2", + "at": "7300ms", + "probe": "console", + "expect": { + "contains": "example: Timer alarmed, count=2000001\r\n" + } + }, + { + "id": "alarm3-not-early", + "at": "8200ms", + "probe": "console", + "expect": { + "not_contains": "example: Timer alarmed, count=3000001" + } + }, + { + "id": "alarm3", + "at": "8300ms", + "probe": "console", + "expect": { + "contains": "example: Timer alarmed, count=3000001\r\n" + } + }, + { + "id": "alarm4-not-early", + "at": "9200ms", + "probe": "console", + "expect": { + "not_contains": "example: Timer alarmed, count=4000001" + } + }, + { + "id": "alarm4", + "at": "9300ms", + "probe": "console", + "expect": { + "contains": "example: Timer alarmed, count=4000001\r\n" + } + }, + { + "id": "deleted", + "at": "9600ms", + "probe": "console", + "expect": { + "contains": "example: Delete timer" + } + }, + { + "id": "no-miss", + "at": "9600ms", + "probe": "console", + "expect": { + "not_contains": "Missed one count event" + } + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/examples/esp32_real_time_stats/project.json b/tests/vendor/esp-idf/examples/esp32_real_time_stats/project.json new file mode 100644 index 000000000..1b585c2b9 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_real_time_stats/project.json @@ -0,0 +1,61 @@ +{ + "schema_version": 1, + "kind": "project", + "name": "esp-idf-esp32_real_time_stats", + "description": "ESP-IDF's system/freertos/real_time_stats on the ESP32-DevKitC V4, plugged into USB; the console is UART0 through the CP2102N.", + "policy": "electrical", + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc", + "layout": { + "x": 360, + "y": 120 + }, + "firmware": { + "image": "flash.bin", + "format": "bin" + } + } + ], + "components": [ + { + "id": "USB", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5 + }, + "layout": { + "x": 120, + "y": 160 + } + }, + { + "id": "GND1", + "component": "emblink/gnd", + "layout": { + "x": 120, + "y": 320 + } + } + ], + "nets": { + "GND": [ + "USB.-", + "GND1.GND", + "B1.GND1" + ], + "USB5V": [ + "USB.+", + "B1.VBUS" + ] + }, + "probes": [ + { + "id": "console", + "target": "serial:B1", + "kind": "serial" + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/examples/esp32_real_time_stats/scenario.json b/tests/vendor/esp-idf/examples/esp32_real_time_stats/scenario.json new file mode 100644 index 000000000..31671eb94 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_real_time_stats/scenario.json @@ -0,0 +1,90 @@ +{ + "schema_version": 1, + "kind": "scenario", + "name": "esp32_real_time_stats", + "until": "3500ms", + "description": "real_time_stats (FreeRTOS's run-time statistics on esp_timer's microseconds): six spin tasks of 500000 iterations and 100 ms sleeps, measured over 100 ticks (1 s) twice. The table's run times are the core's timing model's (each spin about 30 ms on either core at 160 MHz, about ten cycles an iteration from the flash cache), so the scenario checks the table's rows, not the figures: every spin task and both cores' idle tasks (IDLE0, IDLE1) listed, main deleted, the stats obtained each time. ESP-IDF's console turns each \\n into \\r\\n.", + "inputs": [], + "assertions": [ + { + "id": "first", + "at": "600ms", + "probe": "console", + "expect": { + "contains": "Getting real time stats over 100 ticks\r\n" + } + }, + { + "id": "table", + "at": "1500ms", + "probe": "console", + "expect": { + "contains": "| Task | Run Time | Percentage\r\n" + } + }, + { + "id": "spin0", + "at": "1500ms", + "probe": "console", + "expect": { + "contains": "| spin0 | " + } + }, + { + "id": "spin5", + "at": "1500ms", + "probe": "console", + "expect": { + "contains": "| spin5 | " + } + }, + { + "id": "idle0", + "at": "1500ms", + "probe": "console", + "expect": { + "contains": "| IDLE0 | " + } + }, + { + "id": "idle1", + "at": "1500ms", + "probe": "console", + "expect": { + "contains": "| IDLE1 | " + } + }, + { + "id": "main-deleted", + "at": "1500ms", + "probe": "console", + "expect": { + "contains": "| main | Deleted\r\n" + } + }, + { + "id": "obtained", + "at": "1500ms", + "probe": "console", + "expect": { + "contains": "Real time stats obtained\r\n" + } + }, + { + "id": "second", + "at": "3500ms", + "probe": "console", + "expect": { + "contains": "Real time stats obtained\r\n\r\n\r\nGetting real time stats over 100 ticks\r\n" + } + }, + { + "id": "no-error", + "at": "3500ms", + "probe": "console", + "expect": { + "not_contains": "Error getting real time stats" + } + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/examples/esp32_uart_echo/project.json b/tests/vendor/esp-idf/examples/esp32_uart_echo/project.json new file mode 100644 index 000000000..ceed1c3d4 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_uart_echo/project.json @@ -0,0 +1,91 @@ +{ + "schema_version": 1, + "kind": "project", + "name": "esp-idf-esp32_uart_echo", + "description": "ESP-IDF's uart/uart_echo on the ESP32-DevKitC V4: UART2 (the example's port on the ESP32) on TXD IO4 and RXD IO5 at 115200 baud, the example's defaults, to a 3.3 V USB-serial adapter's terminal.", + "policy": "electrical", + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc", + "layout": { + "x": 360, + "y": 120 + }, + "firmware": { + "image": "flash.bin", + "format": "bin" + } + } + ], + "components": [ + { + "id": "USB", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5 + }, + "layout": { + "x": 120, + "y": 160 + } + }, + { + "id": "GND1", + "component": "emblink/gnd", + "layout": { + "x": 120, + "y": 320 + } + }, + { + "id": "VT1", + "component": "emblink/virtual_terminal", + "params": { + "baud": 115200 + }, + "layout": { + "x": 120, + "y": 460 + } + } + ], + "nets": { + "USB5V": [ + "USB.+", + "B1.VBUS" + ], + "GND": [ + "USB.-", + "GND1.GND", + "B1.GND1", + "VT1.GND" + ], + "U2TXD": [ + "B1.IO4", + "VT1.RX" + ], + "U2RXD": [ + "B1.IO5", + "VT1.TX" + ] + }, + "probes": [ + { + "id": "console", + "target": "serial:B1", + "kind": "serial" + }, + { + "id": "sent", + "target": "serial:VT1", + "kind": "serial" + }, + { + "id": "received", + "target": "output:VT1.rx_text", + "kind": "value" + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/examples/esp32_uart_echo/scenario.json b/tests/vendor/esp-idf/examples/esp32_uart_echo/scenario.json new file mode 100644 index 000000000..c8b4c25c6 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_uart_echo/scenario.json @@ -0,0 +1,71 @@ +{ + "schema_version": 1, + "kind": "scenario", + "name": "esp32_uart_echo", + "until": "1200ms", + "description": "uart_echo: what the terminal sends to IO5 comes back on IO4, and the console logs it. The echo task reads with a 20 ms timeout (two FreeRTOS ticks), so a line typed at once comes back whole.", + "inputs": [ + { + "at": "500ms", + "target": "VT1", + "input": "tx_text", + "value": "hello EmbSIM" + }, + { + "at": "900ms", + "target": "VT1", + "input": "tx_text", + "value": "0123456789" + } + ], + "assertions": [ + { + "id": "quiet", + "at": "450ms", + "probe": "received", + "expect": { + "equals": "" + } + }, + { + "id": "sent", + "at": "600ms", + "probe": "sent", + "expect": { + "equals": "hello EmbSIM" + } + }, + { + "id": "echo", + "at": "600ms", + "probe": "received", + "expect": { + "equals": "hello EmbSIM" + } + }, + { + "id": "logged", + "at": "600ms", + "probe": "console", + "expect": { + "contains": "UART TEST: Recv str: hello EmbSIM\r\n" + } + }, + { + "id": "again", + "at": "1200ms", + "probe": "received", + "expect": { + "equals": "hello EmbSIM0123456789" + } + }, + { + "id": "logged-again", + "at": "1200ms", + "probe": "console", + "expect": { + "contains": "UART TEST: Recv str: 0123456789\r\n" + } + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/run.py b/tests/vendor/esp-idf/run.py index d5cd3d58d..63712fede 100644 --- a/tests/vendor/esp-idf/run.py +++ b/tests/vendor/esp-idf/run.py @@ -75,6 +75,9 @@ "esp32_blink": "examples/get-started/blink", "esp32_ledc_basic": "examples/peripherals/ledc/ledc_basic", "esp32_i2c_eeprom": "examples/peripherals/i2c/i2c_eeprom", + "esp32_gptimer": "examples/peripherals/timer_group/gptimer", + "esp32_uart_echo": "examples/peripherals/uart/uart_echo", + "esp32_real_time_stats": "examples/system/freertos/real_time_stats", } # the target an example is built for, when not the ESP32-C3 (its board in # examples//project.json), and the toolchain that target needs @@ -83,6 +86,9 @@ "esp32_blink": "esp32", "esp32_ledc_basic": "esp32", "esp32_i2c_eeprom": "esp32", + "esp32_gptimer": "esp32", + "esp32_uart_echo": "esp32", + "esp32_real_time_stats": "esp32", } TOOLCHAINS = {"esp32c3": "riscv32-esp-elf-gcc", "esp32": "xtensa-esp32-elf-gcc"} # examples built only for another's project (with examples//'s From d337a4efe04fc666d8ca62a43a71579b2b3a74c9 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Sun, 8 Nov 2026 04:50:12 +0000 Subject: [PATCH 52/57] ESP32: the RMT, with ESP-IDF's blink on a WS2812B The ESP32's remote control peripheral from the C3's model on the ESP32's registers: eight channels each sending or receiving through the matrix (rmt_sig_out0-7, rmt_sig_in0-7), 64-word blocks of the 512-word RAM (added to the register file with svd2regs' --memory: the SVD leaves it out), the clock from APB_CLK or REF_TICK, end markers, continuous mode, wrap, TX_LIM, the carrier, the receiver's idle threshold and filter, CHnDATA, three interrupt bits a channel. ESP-IDF's get-started/blink with its LED-strip option runs unmodified on the DevKitC with a WS2812B on IO18 (esp32_blink_strip 7/7). --- CHANGELOG.md | 5 +- catalog/mcus/espressif/esp32/mcu.json | 19 +- catalog/mcus/espressif/esp32/registers.json | 6100 ++++++++++++++++- core/mcu/esp32/soc/esp32.h | 3 + core/mcu/esp32/soc/rmt.c | 679 ++ core/mcu/models.c | 1 + docs/STATUS.md | 5 +- docs/coverage.md | 8 +- .../examples/esp32_blink_strip/project.json | 100 + .../examples/esp32_blink_strip/scenario.json | 66 + .../esp32_blink_strip/sdkconfig.defaults | 3 + tests/vendor/esp-idf/run.py | 2 + 12 files changed, 6743 insertions(+), 248 deletions(-) create mode 100644 core/mcu/esp32/soc/rmt.c create mode 100644 tests/vendor/esp-idf/examples/esp32_blink_strip/project.json create mode 100644 tests/vendor/esp-idf/examples/esp32_blink_strip/scenario.json create mode 100644 tests/vendor/esp-idf/examples/esp32_blink_strip/sdkconfig.defaults diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e3f4da43..d4a82d03a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,14 +21,15 @@ versions follow semantic versioning. There is no release yet. interrupt matrix), RTC_CNTL, eFuse, APB_CTRL clocks, GPIO/IO_MUX with strapping, UART0-2, TIMG0/1, SPI0/1 with the GT25Q32A flash, SHA, LEDC, I2C0/1 (master and slave, 16 commands, APB_CLK timing; DPORT's - PERIP_RST_EN resets them), the internal analog I2C, RNG and the AHB view. + PERIP_RST_EN resets them), the RMT's eight channels, the internal analog I2C, RNG and the AHB view. - Added the `emblink/esp32_devkitc` board (ESP32-DevKitC V4, WROOM-32E) with its power path, LED, Boot and EN buttons, headers and fixtures. - Espressif's ROM, ESP-IDF v5.4's bootloader and hello_world run unmodified through `esp_restart` (`tests/vendor/esp-idf`: `esp32_hello_world` 9/9, `esp32_blink` 6/6, `esp32_ledc_basic` 2/2, `esp32_i2c_eeprom` 3/3 on a 24LC256, `esp32_gptimer` 17/17, `esp32_uart_echo` 6/6 on UART2, - `esp32_real_time_stats` 10/10 across both cores; the UARTs keep + `esp32_real_time_stats` 10/10 across both cores, + `esp32_blink_strip` 7/7 on a WS2812B through the RMT; the UARTs keep MEM_RX_STATUS's addresses, from which ESP-IDF counts the bytes waiting); `test_lx6` checks the core with programs from Espressif's assembler. ### Original ESP32 digital per-pin hold diff --git a/catalog/mcus/espressif/esp32/mcu.json b/catalog/mcus/espressif/esp32/mcu.json index f8e536095..587b267dc 100644 --- a/catalog/mcus/espressif/esp32/mcu.json +++ b/catalog/mcus/espressif/esp32/mcu.json @@ -199,6 +199,10 @@ { "match": "I2C?", "model": "esp32-i2c" + }, + { + "match": "RMT", + "model": "esp32-rmt" } ], "io": { @@ -267,13 +271,14 @@ }, "fidelity": { "functional": { - "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts, MEM_RX_STATUS's addresses that ESP-IDF counts the bytes waiting from), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), LEDC (eight high-speed and eight low-speed PWM channels on eight timers, fading, on the matrix's ledc_hs/ls_sig_out), I2C0 and I2C1 (master and slave on the open-drain lines through the matrix, the 16 commands with RSTART, WRITE, READ, STOP and END, the bus timing in APB_CLK cycles, ACK checks, arbitration, the SCL timeout, the 32-byte FIFOs through DATA and its AHB view, 7- and 10-bit slave addresses; ESP-IDF's i2c_eeprom runs on a 24LC256), PERIP_RST_EN's resets of I2C0, I2C1 and LEDC, the internal analog I2C master, the random number generator and the peripherals' AHB view. The evidence: test_lx6.c's programs assembled by Espressif's assembler (integer, shifts, multiply and divide, loops, branches, memory with S32C1I, windowed recursion through the ROM's own window vectors, SYSCALL, the CCOMPARE timer interrupt, FPU, MAC16) and the APP_CPU's ROM data kept apart; ESP-IDF v5.4's hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo and real_time_stats built unmodified for the esp32 target on the ESP32-DevKitC V4, from the ROM's banner to esp_restart; the ROM image equals Espressif QEMU's esp32-v3-rom.bin byte for byte, and the APP_CPU's view of it QEMU's esp32-v3-rom-app.bin.", + "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts, MEM_RX_STATUS's addresses that ESP-IDF counts the bytes waiting from), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), LEDC (eight high-speed and eight low-speed PWM channels on eight timers, fading, on the matrix's ledc_hs/ls_sig_out), I2C0 and I2C1 (master and slave on the open-drain lines through the matrix, the 16 commands with RSTART, WRITE, READ, STOP and END, the bus timing in APB_CLK cycles, ACK checks, arbitration, the SCL timeout, the 32-byte FIFOs through DATA and its AHB view, 7- and 10-bit slave addresses; ESP-IDF's i2c_eeprom runs on a 24LC256), the RMT (eight channels sending or receiving through the matrix, 64-word blocks of the 512-word RAM, end markers, continuous mode, wrap, TX_LIM's threshold, the carrier, the receiver's idle threshold and filter, the RAM through CHnDATA; ESP-IDF's led_strip lights a WS2812B), PERIP_RST_EN's resets of I2C0, I2C1 and LEDC, the internal analog I2C master, the random number generator and the peripherals' AHB view. The evidence: test_lx6.c's programs assembled by Espressif's assembler (integer, shifts, multiply and divide, loops, branches, memory with S32C1I, windowed recursion through the ROM's own window vectors, SYSCALL, the CCOMPARE timer interrupt, FPU, MAC16) and the APP_CPU's ROM data kept apart; ESP-IDF v5.4's hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo, real_time_stats and blink with its LED strip built unmodified for the esp32 target on the ESP32-DevKitC V4, from the ROM's banner to esp_restart; the ROM image equals Espressif QEMU's esp32-v3-rom.bin byte for byte, and the APP_CPU's view of it QEMU's esp32-v3-rom-app.bin.", "omissions": [ "Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written)", "the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept", "PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts", + "RMT: the memory's power-down (MEM_PD), STATUS's STATE and the APB-side error bits, the carrier on an idle output", "I2C: the non-FIFO mode (NONFIFO_EN, RX_REC_FULL, TX_SEND_EMPTY), SAMPLE_SCL_LEVEL, the input filters (only their share of the SCL high time), 10-bit addressing's R/W check", - "the peripherals without a model yet: I2S0/1, SPI2/3 (HSPI, VSPI), RMT, MCPWM, PCNT, TWAI, SDMMC, EMAC, the SAR ADCs, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption", + "the peripherals without a model yet: I2S0/1, SPI2/3 (HSPI, VSPI), MCPWM, PCNT, TWAI, SDMMC, EMAC, the SAR ADCs, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption", "sleep modes (light and deep sleep, the power domains), brown-out", "the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection" ], @@ -286,7 +291,8 @@ "repo:tests/vendor/esp-idf/examples/esp32_i2c_eeprom/scenario.json", "repo:tests/vendor/esp-idf/examples/esp32_gptimer/scenario.json", "repo:tests/vendor/esp-idf/examples/esp32_uart_echo/scenario.json", - "repo:tests/vendor/esp-idf/examples/esp32_real_time_stats/scenario.json" + "repo:tests/vendor/esp-idf/examples/esp32_real_time_stats/scenario.json", + "repo:tests/vendor/esp-idf/examples/esp32_blink_strip/scenario.json" ] }, "timed": { @@ -392,17 +398,18 @@ }, "i18n": { "fr": { - "source": "0ba7d2d0d3b6", + "source": "f8310c3fa74c", "description": "Espressif ESP32 (ESP32-D0WD-V3, révision de puce v3.1) : deux cœurs Xtensa LX6 jusqu'à 240 MHz, 520 Kio de SRAM, 448 Kio de ROM, 16 Kio de mémoire RTC, une flash SPI externe derrière les caches des cœurs, Wi-Fi et Bluetooth ; le MCU de l'ESP32-DevKitC. La ROM d'Espressif, le bootloader de second niveau d'ESP-IDF et les applications ESP-IDF tournent sur les deux cœurs ; les radios ne sont pas encore modélisées.", "fidelity": { "functional": { - "claims": "Les deux cœurs Xtensa LX6 (mcu/xtensa/lx6.c, lx6fp.c) tels que les configure le core-isa.h d'Espressif : les instructions de base et de densité, les registres fenêtrés (64, le contrôle de débordement de chaque instruction, CALL4/8/12, ENTRY, RETW et son sous-débordement, MOVSP, ROTW, L32E/S32E), les boucles sans surcoût, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, les booléens, S32C1I avec SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), le paquet MAC16, la FPU simple précision (modes d'arrondi et drapeaux IEEE ; les séquences de division et de racine carrée qu'exécute libgcc donnent des résultats correctement arrondis), les exceptions XEA2 avec vecteurs relocalisables, 32 interruptions aux niveaux et types de core-isa.h (niveau, front, logicielles, les temporisateurs CCOMPARE0-2 contre CCOUNT, NMI), RSIL, WAITI, RFI, BREAK, IBREAK, DBREAK et ICOUNT de l'option de débogage, la protection par régions (huit régions de 512 Mo, 15 sans accès), les chargements et écritures non alignés, et les accès de données en 32 bits seulement du bus d'instructions. Les deux cœurs partagent la machine, l'APP_CPU maintenu en reset jusqu'à ce que DPORT le libère et voyant la ROM avec ses pointeurs vers les données ROM du PRO_CPU déplacés vers les siennes, comme dans l'image ROM APP_CPU d'Espressif. Le démarrage : la ROM d'Espressif (esp32_rev300_rom.elf, révision v3) depuis le vecteur de reset, sa bannière, les broches de configuration (GPIO_STRAP : SPI_FAST_FLASH_BOOT, ou le mode téléchargement avec GPIO0 bas), les causes de reset (RTC_CNTL), le contrôle anti-glitch de l'eFuse, la lecture de la flash par SPI1, le bootloader de second niveau d'ESP-IDF et sa vérification d'image sur l'accélérateur SHA, la MMU flash des caches, et l'application sur les deux cœurs. Les modèles : DPORT (reset, horloge et blocage de l'APP_CPU, CPU_PER_CONF, vidage et état des caches, tables de la MMU flash, les 69 sources de la matrice d'interruptions, les interruptions inter-CPU), RTC_CNTL (causes de reset, le temporisateur RTC, le chien de garde RTC avec sa protection au démarrage sur flash, les resets et blocages logiciels, les registres de stockage, SOC_CLK_SEL), APB_CTRL (les horloges CPU et APB depuis XTAL, PLL ou RC_FAST), le contrôleur eFuse avec les bits d'usine de la puce (ESP32-D0WD-V3 v3.1, une MAC avec son CRC, ou celle des réglages de la carte), GPIO et IO_MUX (40 plots, la matrice, tirages, drain ouvert, interruptions sur chaque CPU, le verrou de configuration), UART0-2 (FIFO, trames au débit, interruptions, les adresses de MEM_RX_STATUS d'après lesquelles ESP-IDF compte les octets en attente), TIMG0/1 (T0, T1, le temporisateur LAC, le MWDT avec sa protection au démarrage sur flash, la calibration de RTC_SLOW_CLK), SPI0/SPI1 avec la flash Giantec GT25Q32A du module (C4 60 16, l'identifiant que rapporte la carte du propriétaire), l'accélérateur SHA (SHA-1, -256, -384, -512), LEDC (huit canaux MLI rapides et huit lents sur huit temporisateurs, le fondu, sur ledc_hs/ls_sig_out de la matrice), I2C0 et I2C1 (maître et esclave sur les lignes à drain ouvert à travers la matrice, les 16 commandes avec RSTART, WRITE, READ, STOP et END, le timing du bus en cycles d'APB_CLK, les contrôles d'ACK, l'arbitrage, le délai d'attente sur SCL, les FIFO de 32 octets par DATA et sa vue AHB, les adresses esclaves sur 7 et 10 bits ; l'exemple i2c_eeprom d'ESP-IDF fonctionne avec une 24LC256), les resets de PERIP_RST_EN pour I2C0, I2C1 et LEDC, le maître I2C analogique interne, le générateur de nombres aléatoires et la vue AHB des périphériques. Les preuves : les programmes de test_lx6.c assemblés par l'assembleur d'Espressif (entiers, décalages, multiplication et division, boucles, branchements, mémoire avec S32C1I, récursion fenêtrée à travers les vecteurs de fenêtre de la ROM, SYSCALL, l'interruption du temporisateur CCOMPARE, FPU, MAC16) et les données ROM de l'APP_CPU tenues à part ; hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo et real_time_stats d'ESP-IDF v5.4 compilés sans modification pour la cible esp32 sur l'ESP32-DevKitC V4, de la bannière de la ROM jusqu'à esp_restart ; l'image ROM est identique octet pour octet à l'esp32-v3-rom.bin du QEMU d'Espressif, et la vue qu'en a l'APP_CPU à son esp32-v3-rom-app.bin.", + "claims": "Les deux cœurs Xtensa LX6 (mcu/xtensa/lx6.c, lx6fp.c) tels que les configure le core-isa.h d'Espressif : les instructions de base et de densité, les registres fenêtrés (64, le contrôle de débordement de chaque instruction, CALL4/8/12, ENTRY, RETW et son sous-débordement, MOVSP, ROTW, L32E/S32E), les boucles sans surcoût, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, les booléens, S32C1I avec SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), le paquet MAC16, la FPU simple précision (modes d'arrondi et drapeaux IEEE ; les séquences de division et de racine carrée qu'exécute libgcc donnent des résultats correctement arrondis), les exceptions XEA2 avec vecteurs relocalisables, 32 interruptions aux niveaux et types de core-isa.h (niveau, front, logicielles, les temporisateurs CCOMPARE0-2 contre CCOUNT, NMI), RSIL, WAITI, RFI, BREAK, IBREAK, DBREAK et ICOUNT de l'option de débogage, la protection par régions (huit régions de 512 Mo, 15 sans accès), les chargements et écritures non alignés, et les accès de données en 32 bits seulement du bus d'instructions. Les deux cœurs partagent la machine, l'APP_CPU maintenu en reset jusqu'à ce que DPORT le libère et voyant la ROM avec ses pointeurs vers les données ROM du PRO_CPU déplacés vers les siennes, comme dans l'image ROM APP_CPU d'Espressif. Le démarrage : la ROM d'Espressif (esp32_rev300_rom.elf, révision v3) depuis le vecteur de reset, sa bannière, les broches de configuration (GPIO_STRAP : SPI_FAST_FLASH_BOOT, ou le mode téléchargement avec GPIO0 bas), les causes de reset (RTC_CNTL), le contrôle anti-glitch de l'eFuse, la lecture de la flash par SPI1, le bootloader de second niveau d'ESP-IDF et sa vérification d'image sur l'accélérateur SHA, la MMU flash des caches, et l'application sur les deux cœurs. Les modèles : DPORT (reset, horloge et blocage de l'APP_CPU, CPU_PER_CONF, vidage et état des caches, tables de la MMU flash, les 69 sources de la matrice d'interruptions, les interruptions inter-CPU), RTC_CNTL (causes de reset, le temporisateur RTC, le chien de garde RTC avec sa protection au démarrage sur flash, les resets et blocages logiciels, les registres de stockage, SOC_CLK_SEL), APB_CTRL (les horloges CPU et APB depuis XTAL, PLL ou RC_FAST), le contrôleur eFuse avec les bits d'usine de la puce (ESP32-D0WD-V3 v3.1, une MAC avec son CRC, ou celle des réglages de la carte), GPIO et IO_MUX (40 plots, la matrice, tirages, drain ouvert, interruptions sur chaque CPU, le verrou de configuration), UART0-2 (FIFO, trames au débit, interruptions, les adresses de MEM_RX_STATUS d'après lesquelles ESP-IDF compte les octets en attente), TIMG0/1 (T0, T1, le temporisateur LAC, le MWDT avec sa protection au démarrage sur flash, la calibration de RTC_SLOW_CLK), SPI0/SPI1 avec la flash Giantec GT25Q32A du module (C4 60 16, l'identifiant que rapporte la carte du propriétaire), l'accélérateur SHA (SHA-1, -256, -384, -512), LEDC (huit canaux MLI rapides et huit lents sur huit temporisateurs, le fondu, sur ledc_hs/ls_sig_out de la matrice), I2C0 et I2C1 (maître et esclave sur les lignes à drain ouvert à travers la matrice, les 16 commandes avec RSTART, WRITE, READ, STOP et END, le timing du bus en cycles d'APB_CLK, les contrôles d'ACK, l'arbitrage, le délai d'attente sur SCL, les FIFO de 32 octets par DATA et sa vue AHB, les adresses esclaves sur 7 et 10 bits ; l'exemple i2c_eeprom d'ESP-IDF fonctionne avec une 24LC256), le RMT (huit canaux qui émettent ou reçoivent à travers la matrice, des blocs de 64 mots de la RAM de 512 mots, les marqueurs de fin, le mode continu, le rebouclage, le seuil de TX_LIM, la porteuse, le seuil d'inactivité et le filtre du récepteur, la RAM par CHnDATA ; le led_strip d'ESP-IDF allume une WS2812B), les resets de PERIP_RST_EN pour I2C0, I2C1 et LEDC, le maître I2C analogique interne, le générateur de nombres aléatoires et la vue AHB des périphériques. Les preuves : les programmes de test_lx6.c assemblés par l'assembleur d'Espressif (entiers, décalages, multiplication et division, boucles, branchements, mémoire avec S32C1I, récursion fenêtrée à travers les vecteurs de fenêtre de la ROM, SYSCALL, l'interruption du temporisateur CCOMPARE, FPU, MAC16) et les données ROM de l'APP_CPU tenues à part ; hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo, real_time_stats et blink avec son ruban de LED d'ESP-IDF v5.4 compilés sans modification pour la cible esp32 sur l'ESP32-DevKitC V4, de la bannière de la ROM jusqu'à esp_restart ; l'image ROM est identique octet pour octet à l'esp32-v3-rom.bin du QEMU d'Espressif, et la vue qu'en a l'APP_CPU à son esp32-v3-rom-app.bin.", "omissions": [ "le Wi-Fi, le Bluetooth et l'étage RF (WIFI_MAC, BB, NRX, FE : leurs registres gardent ce qui y est écrit)", "les instructions de l'accélérateur double précision (F64ADDC, F64ITER et les autres : leurs encodages sont publics, pas ce qu'elles font ; un programme qui en exécute une s'arrête avec un diagnostic), bien que ses registres soient conservés", "la PSRAM (DRAM1), le remappage de démarrage, les permissions d'accès APB/AHB, les régions verrouillées et le timing des caches (les pages mappées sont lues entières), les interruptions d'erreur d'accès de la MMU", + "RMT : la mise hors tension de la mémoire (MEM_PD), le champ STATE de STATUS et les bits d'erreur côté APB, la porteuse sur une sortie inactive", "I2C : le mode hors FIFO (NONFIFO_EN, RX_REC_FULL, TX_SEND_EMPTY), SAMPLE_SCL_LEVEL, les filtres d'entrée (seulement leur part du temps haut de SCL), le contrôle R/W de l'adressage sur 10 bits", - "les périphériques sans modèle pour l'instant : I2S0/1, SPI2/3 (HSPI, VSPI), RMT, MCPWM, PCNT, TWAI, SDMMC, EMAC, les ADC SAR, les DAC, le capteur tactile, l'IO MUX RTC et le coprocesseur ULP, les accélérateurs AES et RSA, le chiffrement de la flash", + "les périphériques sans modèle pour l'instant : I2S0/1, SPI2/3 (HSPI, VSPI), MCPWM, PCNT, TWAI, SDMMC, EMAC, les ADC SAR, les DAC, le capteur tactile, l'IO MUX RTC et le coprocesseur ULP, les accélérateurs AES et RSA, le chiffrement de la flash", "les modes de veille (veille légère et profonde, les domaines d'alimentation), la détection de baisse de tension", "le contrôle du schéma de codage 3/4 de l'eFuse, la protection en écriture par champ du BLOCK0" ] diff --git a/catalog/mcus/espressif/esp32/registers.json b/catalog/mcus/espressif/esp32/registers.json index eca92b1ef..b6a5b7d95 100644 --- a/catalog/mcus/espressif/esp32/registers.json +++ b/catalog/mcus/espressif/esp32/registers.json @@ -4,7 +4,7 @@ "id": "espressif/esp32/registers", "version": "1.0.0", "device": "ESP32", - "source": "converted from esp32.svd by tools/importers/svd2regs.py; peripherals left out: RNG (the random number generator's one register (RNG_DATA, TRM 26), which the SVD puts at the AHB address 0x60035144; the chip's model answers at 0x3FF75144 and its AHB alias (misc.c)); fields corrected from the reference manual: DPORT.WIFI_CLK_EN.WIFI_CLK_EN:0:32; fields reserved by the reference manual taken out: GPIO.CPUSDIO_INT1.PIN_PAD_DRIVER, GPIO.CPUSDIO_INT1.PIN_INT_TYPE, GPIO.CPUSDIO_INT1.PIN_WAKEUP_ENABLE, GPIO.CPUSDIO_INT1.PIN_CONFIG, GPIO.CPUSDIO_INT1.PIN_INT_ENA, SENS.SAR_MEAS_WAIT2.FORCE_XPD_SAR_SW; overlapping fields taken out: DPORT.PRO_DCACHE_DBUG0 (PRO_CACHE_MMU_IA), DPORT.PRO_DCACHE_DBUG3 (PRO_CPU_DISABLED_CACHE_IA), DPORT.APP_DCACHE_DBUG0 (APP_CACHE_MMU_IA), DPORT.APP_DCACHE_DBUG3 (APP_CPU_DISABLED_CACHE_IA), DPORT.PRO_CPU_RECORD_PDEBUGINST (RECORD_PRO_PDEBUGINST), DPORT.PRO_CPU_RECORD_PDEBUGSTATUS (RECORD_PRO_PDEBUGSTATUS, RECORD_PDEBUGSTATUS_INSNTYPE), DPORT.PRO_CPU_RECORD_PDEBUGDATA (RECORD_PRO_PDEBUGDATA, RECORD_PDEBUGDATA_EXCVEC, RECORD_PDEBUGDATA_INSNTYPE_SR, RECORD_PDEBUGDATA_INSNTYPE_RER, RECORD_PDEBUGDATA_INSNTYPE_WER, RECORD_PDEBUGDATA_INSNTYPE_ER, RECORD_PDEBUGDATA_STALL_IPIF, RECORD_PDEBUGDATA_INSNTYPE_RSR, RECORD_PDEBUGDATA_INSNTYPE_WSR, RECORD_PDEBUGDATA_INSNTYPE_XSR, RECORD_PDEBUGDATA_STALL_BPIFETCH, RECORD_PDEBUGDATA_EXCCAUSE, RECORD_PDEBUGDATA_STALL_BANKCONFL), DPORT.PRO_CPU_RECORD_PDEBUGLS0STAT (RECORD_PRO_PDEBUGLS0STAT), DPORT.CACHE_IA_INT_EN (CACHE_IA_INT_EN), UART0.MEM_RX_STATUS (MEM_RX_STATUS), RTC_CNTL.LOW_POWER_ST (LOW_POWER_DIAG0), RTC_CNTL.BROWN_OUT (RTC_MEM_CRC_ADDR, RTC_MEM_CRC_LEN, RTC_MEM_CRC_FINISH), RMT.CH0STATUS (STATUS), RMT.CH1STATUS (STATUS), RMT.CH2STATUS (STATUS), RMT.CH3STATUS (STATUS), RMT.CH4STATUS (STATUS), RMT.CH5STATUS (STATUS), RMT.CH6STATUS (STATUS), RMT.CH7STATUS (STATUS), PCNT.U0_STATUS (CORE_STATUS_U0), PCNT.U1_STATUS (CORE_STATUS_U0), PCNT.U2_STATUS (CORE_STATUS_U0), PCNT.U3_STATUS (CORE_STATUS_U0), PCNT.U4_STATUS (CORE_STATUS_U0), PCNT.U5_STATUS (CORE_STATUS_U0), PCNT.U6_STATUS (CORE_STATUS_U0), PCNT.U7_STATUS (CORE_STATUS_U0)", + "source": "converted from esp32.svd by tools/importers/svd2regs.py; peripherals left out: RNG (the random number generator's one register (RNG_DATA, TRM 26), which the SVD puts at the AHB address 0x60035144; the chip's model answers at 0x3FF75144 and its AHB alias (misc.c)); memories added: RMT.RAM at 0x800, 512 words (the channels' RAM, eight blocks of 64 words (TRM 15.2.2, RMTMEM at 0x3FF56800)); fields corrected from the reference manual: DPORT.WIFI_CLK_EN.WIFI_CLK_EN:0:32; fields reserved by the reference manual taken out: GPIO.CPUSDIO_INT1.PIN_PAD_DRIVER, GPIO.CPUSDIO_INT1.PIN_INT_TYPE, GPIO.CPUSDIO_INT1.PIN_WAKEUP_ENABLE, GPIO.CPUSDIO_INT1.PIN_CONFIG, GPIO.CPUSDIO_INT1.PIN_INT_ENA, SENS.SAR_MEAS_WAIT2.FORCE_XPD_SAR_SW; overlapping fields taken out: DPORT.PRO_DCACHE_DBUG0 (PRO_CACHE_MMU_IA), DPORT.PRO_DCACHE_DBUG3 (PRO_CPU_DISABLED_CACHE_IA), DPORT.APP_DCACHE_DBUG0 (APP_CACHE_MMU_IA), DPORT.APP_DCACHE_DBUG3 (APP_CPU_DISABLED_CACHE_IA), DPORT.PRO_CPU_RECORD_PDEBUGINST (RECORD_PRO_PDEBUGINST), DPORT.PRO_CPU_RECORD_PDEBUGSTATUS (RECORD_PRO_PDEBUGSTATUS, RECORD_PDEBUGSTATUS_INSNTYPE), DPORT.PRO_CPU_RECORD_PDEBUGDATA (RECORD_PRO_PDEBUGDATA, RECORD_PDEBUGDATA_EXCVEC, RECORD_PDEBUGDATA_INSNTYPE_SR, RECORD_PDEBUGDATA_INSNTYPE_RER, RECORD_PDEBUGDATA_INSNTYPE_WER, RECORD_PDEBUGDATA_INSNTYPE_ER, RECORD_PDEBUGDATA_STALL_IPIF, RECORD_PDEBUGDATA_INSNTYPE_RSR, RECORD_PDEBUGDATA_INSNTYPE_WSR, RECORD_PDEBUGDATA_INSNTYPE_XSR, RECORD_PDEBUGDATA_STALL_BPIFETCH, RECORD_PDEBUGDATA_EXCCAUSE, RECORD_PDEBUGDATA_STALL_BANKCONFL), DPORT.PRO_CPU_RECORD_PDEBUGLS0STAT (RECORD_PRO_PDEBUGLS0STAT), DPORT.CACHE_IA_INT_EN (CACHE_IA_INT_EN), UART0.MEM_RX_STATUS (MEM_RX_STATUS), RTC_CNTL.LOW_POWER_ST (LOW_POWER_DIAG0), RTC_CNTL.BROWN_OUT (RTC_MEM_CRC_ADDR, RTC_MEM_CRC_LEN, RTC_MEM_CRC_FINISH), RMT.CH0STATUS (STATUS), RMT.CH1STATUS (STATUS), RMT.CH2STATUS (STATUS), RMT.CH3STATUS (STATUS), RMT.CH4STATUS (STATUS), RMT.CH5STATUS (STATUS), RMT.CH6STATUS (STATUS), RMT.CH7STATUS (STATUS), PCNT.U0_STATUS (CORE_STATUS_U0), PCNT.U1_STATUS (CORE_STATUS_U0), PCNT.U2_STATUS (CORE_STATUS_U0), PCNT.U3_STATUS (CORE_STATUS_U0), PCNT.U4_STATUS (CORE_STATUS_U0), PCNT.U5_STATUS (CORE_STATUS_U0), PCNT.U6_STATUS (CORE_STATUS_U0), PCNT.U7_STATUS (CORE_STATUS_U0)", "interrupts": { "WIFI_MAC": 0, "WIFI_NMI": 1, @@ -32872,134 +32872,80 @@ ] }, { - "name": "CH1CONF0", - "offset": "0x28", - "reset": "0x31100002", + "name": "CH0CONF1", + "offset": "0x24", + "reset": "0x00000F20", "fields": [ [ - "DIV_CNT", + "TX_START", 0, - 8 - ], - [ - "IDLE_THRES", - 8, - 16 - ], - [ - "MEM_SIZE", - 24, - 4 - ], - [ - "CARRIER_EN", - 28, 1 ], [ - "CARRIER_OUT_LV", - 29, + "RX_EN", + 1, 1 ], [ - "MEM_PD", - 30, + "MEM_WR_RST", + 2, 1 ], [ - "CLK_EN", - 31, + "MEM_RD_RST", + 3, 1 - ] - ] - }, - { - "name": "CH2CONF0", - "offset": "0x30", - "reset": "0x31100002", - "fields": [ - [ - "DIV_CNT", - 0, - 8 - ], - [ - "IDLE_THRES", - 8, - 16 - ], - [ - "MEM_SIZE", - 24, - 4 ], [ - "CARRIER_EN", - 28, + "APB_MEM_RST", + 4, 1 ], [ - "CARRIER_OUT_LV", - 29, + "MEM_OWNER", + 5, 1 ], [ - "MEM_PD", - 30, + "TX_CONTI_MODE", + 6, 1 ], [ - "CLK_EN", - 31, + "RX_FILTER_EN", + 7, 1 - ] - ] - }, - { - "name": "CH3CONF0", - "offset": "0x38", - "reset": "0x31100002", - "fields": [ - [ - "DIV_CNT", - 0, - 8 ], [ - "IDLE_THRES", + "RX_FILTER_THRES", 8, - 16 - ], - [ - "MEM_SIZE", - 24, - 4 + 8 ], [ - "CARRIER_EN", - 28, + "REF_CNT_RST", + 16, 1 ], [ - "CARRIER_OUT_LV", - 29, + "REF_ALWAYS_ON", + 17, 1 ], [ - "MEM_PD", - 30, + "IDLE_OUT_LV", + 18, 1 ], [ - "CLK_EN", - 31, + "IDLE_OUT_EN", + 19, 1 ] ] }, { - "name": "CH4CONF0", - "offset": "0x40", + "name": "CH1CONF0", + "offset": "0x28", "reset": "0x31100002", "fields": [ [ @@ -33040,92 +32986,80 @@ ] }, { - "name": "CH5CONF0", - "offset": "0x48", - "reset": "0x31100002", + "name": "CH1CONF1", + "offset": "0x2C", + "reset": "0x00000F20", "fields": [ [ - "DIV_CNT", + "TX_START", 0, - 8 + 1 ], [ - "IDLE_THRES", - 8, - 16 + "RX_EN", + 1, + 1 ], [ - "MEM_SIZE", - 24, - 4 + "MEM_WR_RST", + 2, + 1 ], [ - "CARRIER_EN", - 28, + "MEM_RD_RST", + 3, 1 ], [ - "CARRIER_OUT_LV", - 29, + "APB_MEM_RST", + 4, 1 ], [ - "MEM_PD", - 30, + "MEM_OWNER", + 5, 1 ], [ - "CLK_EN", - 31, + "TX_CONTI_MODE", + 6, 1 - ] - ] - }, - { - "name": "CH6CONF0", - "offset": "0x50", - "reset": "0x31100002", - "fields": [ - [ - "DIV_CNT", - 0, - 8 ], [ - "IDLE_THRES", - 8, - 16 + "RX_FILTER_EN", + 7, + 1 ], [ - "MEM_SIZE", - 24, - 4 + "RX_FILTER_THRES", + 8, + 8 ], [ - "CARRIER_EN", - 28, + "REF_CNT_RST", + 16, 1 ], [ - "CARRIER_OUT_LV", - 29, + "REF_ALWAYS_ON", + 17, 1 ], [ - "MEM_PD", - 30, + "IDLE_OUT_LV", + 18, 1 ], [ - "CLK_EN", - 31, + "IDLE_OUT_EN", + 19, 1 ] ] }, { - "name": "CH7CONF0", - "offset": "0x58", + "name": "CH2CONF0", + "offset": "0x30", "reset": "0x31100002", "fields": [ [ @@ -33166,8 +33100,8 @@ ] }, { - "name": "CH0CONF1", - "offset": "0x24", + "name": "CH2CONF1", + "offset": "0x34", "reset": "0x00000F20", "fields": [ [ @@ -33238,80 +33172,50 @@ ] }, { - "name": "CH1CONF1", - "offset": "0x2C", - "reset": "0x00000F20", + "name": "CH3CONF0", + "offset": "0x38", + "reset": "0x31100002", "fields": [ [ - "TX_START", + "DIV_CNT", 0, - 1 - ], - [ - "RX_EN", - 1, - 1 - ], - [ - "MEM_WR_RST", - 2, - 1 - ], - [ - "MEM_RD_RST", - 3, - 1 - ], - [ - "APB_MEM_RST", - 4, - 1 - ], - [ - "MEM_OWNER", - 5, - 1 - ], - [ - "TX_CONTI_MODE", - 6, - 1 + 8 ], [ - "RX_FILTER_EN", - 7, - 1 + "IDLE_THRES", + 8, + 16 ], [ - "RX_FILTER_THRES", - 8, - 8 + "MEM_SIZE", + 24, + 4 ], [ - "REF_CNT_RST", - 16, + "CARRIER_EN", + 28, 1 ], [ - "REF_ALWAYS_ON", - 17, + "CARRIER_OUT_LV", + 29, 1 ], [ - "IDLE_OUT_LV", - 18, + "MEM_PD", + 30, 1 ], [ - "IDLE_OUT_EN", - 19, + "CLK_EN", + 31, 1 ] ] }, { - "name": "CH2CONF1", - "offset": "0x34", + "name": "CH3CONF1", + "offset": "0x3C", "reset": "0x00000F20", "fields": [ [ @@ -33382,73 +33286,43 @@ ] }, { - "name": "CH3CONF1", - "offset": "0x3C", - "reset": "0x00000F20", + "name": "CH4CONF0", + "offset": "0x40", + "reset": "0x31100002", "fields": [ [ - "TX_START", + "DIV_CNT", 0, - 1 - ], - [ - "RX_EN", - 1, - 1 - ], - [ - "MEM_WR_RST", - 2, - 1 - ], - [ - "MEM_RD_RST", - 3, - 1 - ], - [ - "APB_MEM_RST", - 4, - 1 - ], - [ - "MEM_OWNER", - 5, - 1 - ], - [ - "TX_CONTI_MODE", - 6, - 1 + 8 ], [ - "RX_FILTER_EN", - 7, - 1 + "IDLE_THRES", + 8, + 16 ], [ - "RX_FILTER_THRES", - 8, - 8 + "MEM_SIZE", + 24, + 4 ], [ - "REF_CNT_RST", - 16, + "CARRIER_EN", + 28, 1 ], [ - "REF_ALWAYS_ON", - 17, + "CARRIER_OUT_LV", + 29, 1 ], [ - "IDLE_OUT_LV", - 18, + "MEM_PD", + 30, 1 ], [ - "IDLE_OUT_EN", - 19, + "CLK_EN", + 31, 1 ] ] @@ -33525,6 +33399,48 @@ ] ] }, + { + "name": "CH5CONF0", + "offset": "0x48", + "reset": "0x31100002", + "fields": [ + [ + "DIV_CNT", + 0, + 8 + ], + [ + "IDLE_THRES", + 8, + 16 + ], + [ + "MEM_SIZE", + 24, + 4 + ], + [ + "CARRIER_EN", + 28, + 1 + ], + [ + "CARRIER_OUT_LV", + 29, + 1 + ], + [ + "MEM_PD", + 30, + 1 + ], + [ + "CLK_EN", + 31, + 1 + ] + ] + }, { "name": "CH5CONF1", "offset": "0x4C", @@ -33597,6 +33513,48 @@ ] ] }, + { + "name": "CH6CONF0", + "offset": "0x50", + "reset": "0x31100002", + "fields": [ + [ + "DIV_CNT", + 0, + 8 + ], + [ + "IDLE_THRES", + 8, + 16 + ], + [ + "MEM_SIZE", + 24, + 4 + ], + [ + "CARRIER_EN", + 28, + 1 + ], + [ + "CARRIER_OUT_LV", + 29, + 1 + ], + [ + "MEM_PD", + 30, + 1 + ], + [ + "CLK_EN", + 31, + 1 + ] + ] + }, { "name": "CH6CONF1", "offset": "0x54", @@ -33669,6 +33627,48 @@ ] ] }, + { + "name": "CH7CONF0", + "offset": "0x58", + "reset": "0x31100002", + "fields": [ + [ + "DIV_CNT", + 0, + 8 + ], + [ + "IDLE_THRES", + 8, + 16 + ], + [ + "MEM_SIZE", + 24, + 4 + ], + [ + "CARRIER_EN", + 28, + 1 + ], + [ + "CARRIER_OUT_LV", + 29, + 1 + ], + [ + "MEM_PD", + 30, + 1 + ], + [ + "CLK_EN", + 31, + 1 + ] + ] + }, { "name": "CH7CONF1", "offset": "0x5C", @@ -35288,6 +35288,5638 @@ 32 ] ] + }, + { + "name": "RAM0", + "offset": "0x800", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM1", + "offset": "0x804", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM2", + "offset": "0x808", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM3", + "offset": "0x80C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM4", + "offset": "0x810", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM5", + "offset": "0x814", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM6", + "offset": "0x818", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM7", + "offset": "0x81C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM8", + "offset": "0x820", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM9", + "offset": "0x824", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM10", + "offset": "0x828", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM11", + "offset": "0x82C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM12", + "offset": "0x830", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM13", + "offset": "0x834", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM14", + "offset": "0x838", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM15", + "offset": "0x83C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM16", + "offset": "0x840", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM17", + "offset": "0x844", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM18", + "offset": "0x848", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM19", + "offset": "0x84C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM20", + "offset": "0x850", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM21", + "offset": "0x854", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM22", + "offset": "0x858", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM23", + "offset": "0x85C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM24", + "offset": "0x860", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM25", + "offset": "0x864", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM26", + "offset": "0x868", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM27", + "offset": "0x86C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM28", + "offset": "0x870", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM29", + "offset": "0x874", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM30", + "offset": "0x878", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM31", + "offset": "0x87C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM32", + "offset": "0x880", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM33", + "offset": "0x884", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM34", + "offset": "0x888", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM35", + "offset": "0x88C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM36", + "offset": "0x890", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM37", + "offset": "0x894", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM38", + "offset": "0x898", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM39", + "offset": "0x89C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM40", + "offset": "0x8A0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM41", + "offset": "0x8A4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM42", + "offset": "0x8A8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM43", + "offset": "0x8AC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM44", + "offset": "0x8B0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM45", + "offset": "0x8B4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM46", + "offset": "0x8B8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM47", + "offset": "0x8BC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM48", + "offset": "0x8C0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM49", + "offset": "0x8C4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM50", + "offset": "0x8C8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM51", + "offset": "0x8CC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM52", + "offset": "0x8D0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM53", + "offset": "0x8D4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM54", + "offset": "0x8D8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM55", + "offset": "0x8DC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM56", + "offset": "0x8E0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM57", + "offset": "0x8E4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM58", + "offset": "0x8E8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM59", + "offset": "0x8EC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM60", + "offset": "0x8F0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM61", + "offset": "0x8F4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM62", + "offset": "0x8F8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM63", + "offset": "0x8FC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM64", + "offset": "0x900", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM65", + "offset": "0x904", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM66", + "offset": "0x908", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM67", + "offset": "0x90C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM68", + "offset": "0x910", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM69", + "offset": "0x914", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM70", + "offset": "0x918", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM71", + "offset": "0x91C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM72", + "offset": "0x920", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM73", + "offset": "0x924", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM74", + "offset": "0x928", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM75", + "offset": "0x92C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM76", + "offset": "0x930", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM77", + "offset": "0x934", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM78", + "offset": "0x938", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM79", + "offset": "0x93C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM80", + "offset": "0x940", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM81", + "offset": "0x944", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM82", + "offset": "0x948", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM83", + "offset": "0x94C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM84", + "offset": "0x950", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM85", + "offset": "0x954", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM86", + "offset": "0x958", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM87", + "offset": "0x95C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM88", + "offset": "0x960", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM89", + "offset": "0x964", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM90", + "offset": "0x968", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM91", + "offset": "0x96C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM92", + "offset": "0x970", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM93", + "offset": "0x974", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM94", + "offset": "0x978", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM95", + "offset": "0x97C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM96", + "offset": "0x980", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM97", + "offset": "0x984", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM98", + "offset": "0x988", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM99", + "offset": "0x98C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM100", + "offset": "0x990", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM101", + "offset": "0x994", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM102", + "offset": "0x998", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM103", + "offset": "0x99C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM104", + "offset": "0x9A0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM105", + "offset": "0x9A4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM106", + "offset": "0x9A8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM107", + "offset": "0x9AC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM108", + "offset": "0x9B0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM109", + "offset": "0x9B4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM110", + "offset": "0x9B8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM111", + "offset": "0x9BC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM112", + "offset": "0x9C0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM113", + "offset": "0x9C4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM114", + "offset": "0x9C8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM115", + "offset": "0x9CC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM116", + "offset": "0x9D0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM117", + "offset": "0x9D4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM118", + "offset": "0x9D8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM119", + "offset": "0x9DC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM120", + "offset": "0x9E0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM121", + "offset": "0x9E4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM122", + "offset": "0x9E8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM123", + "offset": "0x9EC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM124", + "offset": "0x9F0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM125", + "offset": "0x9F4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM126", + "offset": "0x9F8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM127", + "offset": "0x9FC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM128", + "offset": "0xA00", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM129", + "offset": "0xA04", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM130", + "offset": "0xA08", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM131", + "offset": "0xA0C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM132", + "offset": "0xA10", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM133", + "offset": "0xA14", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM134", + "offset": "0xA18", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM135", + "offset": "0xA1C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM136", + "offset": "0xA20", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM137", + "offset": "0xA24", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM138", + "offset": "0xA28", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM139", + "offset": "0xA2C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM140", + "offset": "0xA30", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM141", + "offset": "0xA34", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM142", + "offset": "0xA38", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM143", + "offset": "0xA3C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM144", + "offset": "0xA40", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM145", + "offset": "0xA44", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM146", + "offset": "0xA48", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM147", + "offset": "0xA4C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM148", + "offset": "0xA50", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM149", + "offset": "0xA54", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM150", + "offset": "0xA58", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM151", + "offset": "0xA5C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM152", + "offset": "0xA60", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM153", + "offset": "0xA64", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM154", + "offset": "0xA68", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM155", + "offset": "0xA6C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM156", + "offset": "0xA70", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM157", + "offset": "0xA74", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM158", + "offset": "0xA78", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM159", + "offset": "0xA7C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM160", + "offset": "0xA80", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM161", + "offset": "0xA84", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM162", + "offset": "0xA88", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM163", + "offset": "0xA8C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM164", + "offset": "0xA90", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM165", + "offset": "0xA94", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM166", + "offset": "0xA98", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM167", + "offset": "0xA9C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM168", + "offset": "0xAA0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM169", + "offset": "0xAA4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM170", + "offset": "0xAA8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM171", + "offset": "0xAAC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM172", + "offset": "0xAB0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM173", + "offset": "0xAB4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM174", + "offset": "0xAB8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM175", + "offset": "0xABC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM176", + "offset": "0xAC0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM177", + "offset": "0xAC4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM178", + "offset": "0xAC8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM179", + "offset": "0xACC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM180", + "offset": "0xAD0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM181", + "offset": "0xAD4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM182", + "offset": "0xAD8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM183", + "offset": "0xADC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM184", + "offset": "0xAE0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM185", + "offset": "0xAE4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM186", + "offset": "0xAE8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM187", + "offset": "0xAEC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM188", + "offset": "0xAF0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM189", + "offset": "0xAF4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM190", + "offset": "0xAF8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM191", + "offset": "0xAFC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM192", + "offset": "0xB00", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM193", + "offset": "0xB04", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM194", + "offset": "0xB08", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM195", + "offset": "0xB0C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM196", + "offset": "0xB10", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM197", + "offset": "0xB14", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM198", + "offset": "0xB18", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM199", + "offset": "0xB1C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM200", + "offset": "0xB20", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM201", + "offset": "0xB24", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM202", + "offset": "0xB28", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM203", + "offset": "0xB2C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM204", + "offset": "0xB30", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM205", + "offset": "0xB34", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM206", + "offset": "0xB38", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM207", + "offset": "0xB3C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM208", + "offset": "0xB40", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM209", + "offset": "0xB44", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM210", + "offset": "0xB48", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM211", + "offset": "0xB4C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM212", + "offset": "0xB50", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM213", + "offset": "0xB54", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM214", + "offset": "0xB58", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM215", + "offset": "0xB5C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM216", + "offset": "0xB60", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM217", + "offset": "0xB64", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM218", + "offset": "0xB68", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM219", + "offset": "0xB6C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM220", + "offset": "0xB70", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM221", + "offset": "0xB74", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM222", + "offset": "0xB78", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM223", + "offset": "0xB7C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM224", + "offset": "0xB80", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM225", + "offset": "0xB84", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM226", + "offset": "0xB88", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM227", + "offset": "0xB8C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM228", + "offset": "0xB90", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM229", + "offset": "0xB94", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM230", + "offset": "0xB98", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM231", + "offset": "0xB9C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM232", + "offset": "0xBA0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM233", + "offset": "0xBA4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM234", + "offset": "0xBA8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM235", + "offset": "0xBAC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM236", + "offset": "0xBB0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM237", + "offset": "0xBB4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM238", + "offset": "0xBB8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM239", + "offset": "0xBBC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM240", + "offset": "0xBC0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM241", + "offset": "0xBC4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM242", + "offset": "0xBC8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM243", + "offset": "0xBCC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM244", + "offset": "0xBD0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM245", + "offset": "0xBD4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM246", + "offset": "0xBD8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM247", + "offset": "0xBDC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM248", + "offset": "0xBE0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM249", + "offset": "0xBE4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM250", + "offset": "0xBE8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM251", + "offset": "0xBEC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM252", + "offset": "0xBF0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM253", + "offset": "0xBF4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM254", + "offset": "0xBF8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM255", + "offset": "0xBFC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM256", + "offset": "0xC00", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM257", + "offset": "0xC04", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM258", + "offset": "0xC08", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM259", + "offset": "0xC0C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM260", + "offset": "0xC10", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM261", + "offset": "0xC14", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM262", + "offset": "0xC18", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM263", + "offset": "0xC1C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM264", + "offset": "0xC20", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM265", + "offset": "0xC24", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM266", + "offset": "0xC28", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM267", + "offset": "0xC2C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM268", + "offset": "0xC30", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM269", + "offset": "0xC34", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM270", + "offset": "0xC38", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM271", + "offset": "0xC3C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM272", + "offset": "0xC40", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM273", + "offset": "0xC44", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM274", + "offset": "0xC48", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM275", + "offset": "0xC4C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM276", + "offset": "0xC50", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM277", + "offset": "0xC54", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM278", + "offset": "0xC58", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM279", + "offset": "0xC5C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM280", + "offset": "0xC60", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM281", + "offset": "0xC64", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM282", + "offset": "0xC68", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM283", + "offset": "0xC6C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM284", + "offset": "0xC70", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM285", + "offset": "0xC74", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM286", + "offset": "0xC78", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM287", + "offset": "0xC7C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM288", + "offset": "0xC80", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM289", + "offset": "0xC84", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM290", + "offset": "0xC88", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM291", + "offset": "0xC8C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM292", + "offset": "0xC90", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM293", + "offset": "0xC94", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM294", + "offset": "0xC98", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM295", + "offset": "0xC9C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM296", + "offset": "0xCA0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM297", + "offset": "0xCA4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM298", + "offset": "0xCA8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM299", + "offset": "0xCAC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM300", + "offset": "0xCB0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM301", + "offset": "0xCB4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM302", + "offset": "0xCB8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM303", + "offset": "0xCBC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM304", + "offset": "0xCC0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM305", + "offset": "0xCC4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM306", + "offset": "0xCC8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM307", + "offset": "0xCCC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM308", + "offset": "0xCD0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM309", + "offset": "0xCD4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM310", + "offset": "0xCD8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM311", + "offset": "0xCDC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM312", + "offset": "0xCE0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM313", + "offset": "0xCE4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM314", + "offset": "0xCE8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM315", + "offset": "0xCEC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM316", + "offset": "0xCF0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM317", + "offset": "0xCF4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM318", + "offset": "0xCF8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM319", + "offset": "0xCFC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM320", + "offset": "0xD00", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM321", + "offset": "0xD04", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM322", + "offset": "0xD08", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM323", + "offset": "0xD0C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM324", + "offset": "0xD10", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM325", + "offset": "0xD14", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM326", + "offset": "0xD18", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM327", + "offset": "0xD1C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM328", + "offset": "0xD20", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM329", + "offset": "0xD24", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM330", + "offset": "0xD28", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM331", + "offset": "0xD2C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM332", + "offset": "0xD30", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM333", + "offset": "0xD34", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM334", + "offset": "0xD38", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM335", + "offset": "0xD3C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM336", + "offset": "0xD40", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM337", + "offset": "0xD44", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM338", + "offset": "0xD48", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM339", + "offset": "0xD4C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM340", + "offset": "0xD50", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM341", + "offset": "0xD54", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM342", + "offset": "0xD58", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM343", + "offset": "0xD5C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM344", + "offset": "0xD60", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM345", + "offset": "0xD64", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM346", + "offset": "0xD68", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM347", + "offset": "0xD6C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM348", + "offset": "0xD70", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM349", + "offset": "0xD74", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM350", + "offset": "0xD78", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM351", + "offset": "0xD7C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM352", + "offset": "0xD80", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM353", + "offset": "0xD84", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM354", + "offset": "0xD88", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM355", + "offset": "0xD8C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM356", + "offset": "0xD90", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM357", + "offset": "0xD94", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM358", + "offset": "0xD98", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM359", + "offset": "0xD9C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM360", + "offset": "0xDA0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM361", + "offset": "0xDA4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM362", + "offset": "0xDA8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM363", + "offset": "0xDAC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM364", + "offset": "0xDB0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM365", + "offset": "0xDB4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM366", + "offset": "0xDB8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM367", + "offset": "0xDBC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM368", + "offset": "0xDC0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM369", + "offset": "0xDC4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM370", + "offset": "0xDC8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM371", + "offset": "0xDCC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM372", + "offset": "0xDD0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM373", + "offset": "0xDD4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM374", + "offset": "0xDD8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM375", + "offset": "0xDDC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM376", + "offset": "0xDE0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM377", + "offset": "0xDE4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM378", + "offset": "0xDE8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM379", + "offset": "0xDEC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM380", + "offset": "0xDF0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM381", + "offset": "0xDF4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM382", + "offset": "0xDF8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM383", + "offset": "0xDFC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM384", + "offset": "0xE00", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM385", + "offset": "0xE04", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM386", + "offset": "0xE08", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM387", + "offset": "0xE0C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM388", + "offset": "0xE10", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM389", + "offset": "0xE14", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM390", + "offset": "0xE18", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM391", + "offset": "0xE1C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM392", + "offset": "0xE20", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM393", + "offset": "0xE24", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM394", + "offset": "0xE28", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM395", + "offset": "0xE2C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM396", + "offset": "0xE30", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM397", + "offset": "0xE34", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM398", + "offset": "0xE38", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM399", + "offset": "0xE3C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM400", + "offset": "0xE40", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM401", + "offset": "0xE44", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM402", + "offset": "0xE48", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM403", + "offset": "0xE4C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM404", + "offset": "0xE50", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM405", + "offset": "0xE54", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM406", + "offset": "0xE58", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM407", + "offset": "0xE5C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM408", + "offset": "0xE60", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM409", + "offset": "0xE64", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM410", + "offset": "0xE68", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM411", + "offset": "0xE6C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM412", + "offset": "0xE70", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM413", + "offset": "0xE74", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM414", + "offset": "0xE78", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM415", + "offset": "0xE7C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM416", + "offset": "0xE80", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM417", + "offset": "0xE84", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM418", + "offset": "0xE88", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM419", + "offset": "0xE8C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM420", + "offset": "0xE90", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM421", + "offset": "0xE94", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM422", + "offset": "0xE98", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM423", + "offset": "0xE9C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM424", + "offset": "0xEA0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM425", + "offset": "0xEA4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM426", + "offset": "0xEA8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM427", + "offset": "0xEAC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM428", + "offset": "0xEB0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM429", + "offset": "0xEB4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM430", + "offset": "0xEB8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM431", + "offset": "0xEBC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM432", + "offset": "0xEC0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM433", + "offset": "0xEC4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM434", + "offset": "0xEC8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM435", + "offset": "0xECC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM436", + "offset": "0xED0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM437", + "offset": "0xED4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM438", + "offset": "0xED8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM439", + "offset": "0xEDC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM440", + "offset": "0xEE0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM441", + "offset": "0xEE4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM442", + "offset": "0xEE8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM443", + "offset": "0xEEC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM444", + "offset": "0xEF0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM445", + "offset": "0xEF4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM446", + "offset": "0xEF8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM447", + "offset": "0xEFC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM448", + "offset": "0xF00", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM449", + "offset": "0xF04", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM450", + "offset": "0xF08", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM451", + "offset": "0xF0C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM452", + "offset": "0xF10", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM453", + "offset": "0xF14", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM454", + "offset": "0xF18", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM455", + "offset": "0xF1C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM456", + "offset": "0xF20", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM457", + "offset": "0xF24", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM458", + "offset": "0xF28", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM459", + "offset": "0xF2C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM460", + "offset": "0xF30", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM461", + "offset": "0xF34", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM462", + "offset": "0xF38", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM463", + "offset": "0xF3C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM464", + "offset": "0xF40", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM465", + "offset": "0xF44", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM466", + "offset": "0xF48", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM467", + "offset": "0xF4C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM468", + "offset": "0xF50", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM469", + "offset": "0xF54", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM470", + "offset": "0xF58", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM471", + "offset": "0xF5C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM472", + "offset": "0xF60", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM473", + "offset": "0xF64", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM474", + "offset": "0xF68", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM475", + "offset": "0xF6C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM476", + "offset": "0xF70", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM477", + "offset": "0xF74", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM478", + "offset": "0xF78", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM479", + "offset": "0xF7C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM480", + "offset": "0xF80", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM481", + "offset": "0xF84", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM482", + "offset": "0xF88", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM483", + "offset": "0xF8C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM484", + "offset": "0xF90", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM485", + "offset": "0xF94", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM486", + "offset": "0xF98", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM487", + "offset": "0xF9C", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM488", + "offset": "0xFA0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM489", + "offset": "0xFA4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM490", + "offset": "0xFA8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM491", + "offset": "0xFAC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM492", + "offset": "0xFB0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM493", + "offset": "0xFB4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM494", + "offset": "0xFB8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM495", + "offset": "0xFBC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM496", + "offset": "0xFC0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM497", + "offset": "0xFC4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM498", + "offset": "0xFC8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM499", + "offset": "0xFCC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM500", + "offset": "0xFD0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM501", + "offset": "0xFD4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM502", + "offset": "0xFD8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM503", + "offset": "0xFDC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM504", + "offset": "0xFE0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM505", + "offset": "0xFE4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM506", + "offset": "0xFE8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM507", + "offset": "0xFEC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM508", + "offset": "0xFF0", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM509", + "offset": "0xFF4", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM510", + "offset": "0xFF8", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] + }, + { + "name": "RAM511", + "offset": "0xFFC", + "fields": [ + [ + "DATA", + 0, + 32 + ] + ] } ] }, diff --git a/core/mcu/esp32/soc/esp32.h b/core/mcu/esp32/soc/esp32.h index 01aba4180..a6df40ba5 100644 --- a/core/mcu/esp32/soc/esp32.h +++ b/core/mcu/esp32/soc/esp32.h @@ -18,6 +18,7 @@ * sha.c the SHA accelerator * ledc.c the LED PWM controller * i2c.c I2C0 and I2C1 + * rmt.c the remote control peripheral's eight channels * misc.c the random number generator, the AHB view of the * peripherals */ #ifndef ESK_MCU_ESP32_SOC_H @@ -55,6 +56,8 @@ extern const struct esk_dev_ops esk_esp32_ledc_ops; void *esk_esp32_ledc_create(struct esk_mach *s, struct esk_rf_periph *p); extern const struct esk_dev_ops esk_esp32_i2c_ops; void *esk_esp32_i2c_create(struct esk_mach *s, struct esk_rf_periph *p); +extern const struct esk_dev_ops esk_esp32_rmt_ops; +void *esk_esp32_rmt_create(struct esk_mach *s, struct esk_rf_periph *p); extern const struct esk_dev_ops esk_esp32_sha_ops; void *esk_esp32_sha_create(struct esk_mach *s, struct esk_rf_periph *p); /* the RNG and the AHB view of the peripherals (misc.c): 0, or -1 */ diff --git a/core/mcu/esp32/soc/rmt.c b/core/mcu/esp32/soc/rmt.c new file mode 100644 index 000000000..99472863f --- /dev/null +++ b/core/mcu/esp32/soc/rmt.c @@ -0,0 +1,679 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* rmt.c -- the ESP32's remote control peripheral (RMT, TRM v5 chapter 15): + * eight channels, each a transmitter or a receiver (rmt_sig_out0-7, + * signals 87-94 of the GPIO matrix; rmt_sig_in0-7, 83-90), sharing a + * 512 x 32-bit RAM at +0x800, 64 words to a block; matrix source 47. The + * ESP32-C3's model (mcu/esp32c3/rmt.c) on the ESP32's registers. + * + * - A word holds two entries, the low half first: bits 14:0 a period in + * the channel's clock, bit 15 the level; a period of 0 ends the data. + * The channel's clock is APB_CLK (CONF1's REF_ALWAYS_ON 1) or + * REF_TICK's 1 MHz (0), divided by CONF0's DIV_CNT (0 is 256). The + * settings act as written (the ESP32 has no CONF_UPDATE). + * - Channel n uses MEM_SIZE blocks from its own (block n). The CPU + * reaches the RAM at its addresses or, with APB_CONF's APB_FIFO_MASK 0, + * through CHnDATA: a write appends to the channel's memory, a read takes + * its next word; APB_MEM_RST rewinds it (CHnADDR). + * - TX_START sends from the start of the channel's memory: each entry's + * level for its period. An end marker stops it (CHn_TX_END) and leaves + * the output at IDLE_OUT_LV when IDLE_OUT_EN is set, else at the + * marker's level. Past the end of its memory it goes on from the start + * with APB_CONF's MEM_TX_WRAP_EN, else stops with CHn_ERR. Every TX_LIM + * words sent raise CHn_TX_THR_EVENT. In continuous mode (TX_CONTI_MODE) + * an end marker, or the end of the memory, starts the data again. + * - The carrier (CARRIER_EN): while the channel sends the level + * CARRIER_OUT_LV, the output is CARRIER_DUTY's HIGH source-clock cycles + * high and LOW low, from the start of each such stretch. + * - RX_EN starts a receiver: from the input's first edge it writes each + * level and its length in the channel's clock; a level longer than + * IDLE_THRES ends it with an end marker (period 0, the idle level) and + * CHn_RX_END. With RX_FILTER_EN a pulse shorter than RX_FILTER_THRES + * APB_CLK cycles is dropped. At the end of its memory a receiver stops + * with CHn_ERR and MEM_FULL. A receiver whose memory the CPU owns + * (MEM_OWNER 0) writes nothing and sets MEM_OWNER_ERR. + * - CHnSTATUS: MEM_WADDR_EX and MEM_RADDR_EX the RAM's word addresses + * (ESP-IDF counts what a receiver wrote from the first), MEM_FULL, + * MEM_OWNER_ERR. + * - INT_ST is INT_RAW and INT_ENA, raising the source; INT_CLR clears. + * Where it differs inside: the edges are timed from the start of each + * transmission, not from a free-running divider (REF_CNT_RST has nothing + * to reset), and a carrier restarts with each stretch it modulates; the + * receiver's filter does not delay what it passes. Not yet: the memory's + * power-down (MEM_PD), STATUS's STATE and the APB-side error bits, the + * clock gate's effect on a running channel. */ +#include +#include + +#include "mcu/esp32/soc/esp32.h" + +#define SOURCE 47 +#define NCH 8 +#define NWORDS 512 +#define BLOCK 64 +#define SIG_OUT0 87 +#define SIG_IN0 83 +#define REF_TICK_HZ 1000000u +#define FS_PER_S 1000000000000000ull + +/* CONF0 */ +#define CARRIER_EN (1u << 28) +#define CARRIER_LV (1u << 29) +/* CONF1 */ +#define TX_START (1u << 0) +#define RX_EN (1u << 1) +#define MEM_WR_RST (1u << 2) +#define MEM_RD_RST (1u << 3) +#define APB_MEM_RST (1u << 4) +#define MEM_OWNER (1u << 5) +#define TX_CONTI (1u << 6) +#define FILTER_EN (1u << 7) +#define REF_ALWAYS_ON (1u << 17) +#define IDLE_LV (1u << 18) +#define IDLE_EN (1u << 19) +/* APB_CONF */ +#define FIFO_MASK (1u << 0) +#define TX_WRAP (1u << 1) +/* INT_RAW */ +#define I_TX_END(n) (1u << (3 * (n))) +#define I_RX_END(n) (1u << (3 * (n) + 1)) +#define I_ERR(n) (1u << (3 * (n) + 2)) +#define I_TX_THR(n) (1u << (24 + (n))) + +struct ch { + struct esk_rf_reg *conf0, *conf1, *status, *addr, *duty, *lim; + /* transmitting */ + int busy; + u32 e; /* the entry being sent, from the channel's first */ + u64 left; /* fs until the entry's end */ + int level; /* the data's level (while idle: the idle output's) */ + int out; /* the output */ + int carrying; /* the carrier modulates the output now */ + int cphase; /* the carrier's high half */ + u64 cleft; /* fs until the carrier's next edge */ + u32 sent; + /* receiving */ + int on, active, raw, full; + int c_level, p_level; /* the level written next, the input's since p_t */ + u64 c_t, p_t; /* their starts, in core cycles */ + u32 wr; /* entries written, from the channel's first */ + u32 apb; /* CHnDATA's word, from the channel's first */ +}; + +struct e32_rmt { + struct esk_mach *s; + struct esk_rf_periph *p; + struct esk_rf_reg *apb_conf, *raw, *st, *ena, *ram[NWORDS]; + struct ch ch[NCH]; + int clocked, level; +}; + +static u32 reg(const struct esk_rf_reg *r) { return (u32)r->value; } + +static void update(struct e32_rmt *k) +{ + u32 st = reg(k->raw) & reg(k->ena); + int level = st != 0; + esk_rf_hw(k->st, st); + if (level != k->level) { + k->level = level; + esk_esp32_source(k->s, SOURCE, level); + } +} + +static void raise(struct e32_rmt *k, u32 bits) +{ + esk_rf_hw(k->raw, reg(k->raw) | bits); + update(k); +} + +/* the channel's source clock period in fs: APB_CLK or REF_TICK */ +static double src_fs(const struct e32_rmt *k, const struct ch *c) +{ + u32 hz = (reg(c->conf1) & REF_ALWAYS_ON) ? esk_esp32_apb_hz(k->s) : REF_TICK_HZ; + return hz ? (double)FS_PER_S / (double)hz : 0; +} + +/* the channel clock's period in fs: DIV_CNT source cycles */ +static double tick_fs(const struct e32_rmt *k, const struct ch *c) +{ + u32 div = reg(c->conf0) & 0xffu; + return (div ? (double)div : 256.0) * src_fs(k, c); +} + +static u64 fs_per_cycle(const struct e32_rmt *k) +{ + u32 hz = esk_mach_core_hz(k->s); + return hz ? FS_PER_S / hz : 0; +} + +static u64 fs_of(double fs) { return fs <= 0 ? 0 : (u64)(fs + 0.5); } + +static u32 mem_size(const struct ch *c) +{ + u32 n = (reg(c->conf0) >> 24) & 15u; + return n ? n : 1u; +} + +static u32 entry(const struct e32_rmt *k, int n, u32 e) +{ + u32 w = (u32)BLOCK * (u32)n + e / 2u, v; + if (w >= NWORDS) + return 0; + v = reg(k->ram[w]); + return e & 1u ? v >> 16 : v & 0xffffu; +} + +static void status(struct e32_rmt *k, int n) +{ + struct ch *c = &k->ch[n]; + u32 raddr = (u32)BLOCK * (u32)n + c->e / 2u, waddr = (u32)BLOCK * (u32)n + (c->wr + 1u) / 2u; + u32 v = (waddr & 0x3ffu) | (raddr & 0x3ffu) << 12; + if (c->full) + v |= 1u << 28; + if (c->on && c->active && !(reg(c->conf1) & MEM_OWNER)) + v |= 1u << 27; + esk_rf_hw(c->status, v); + esk_rf_hw(c->addr, 0x3FF56800u + 4u * ((u32)BLOCK * (u32)n + c->apb)); +} + +/* ---- transmitting ------------------------------------------------------------ */ + +static void tx_drive(struct e32_rmt *k, int n) +{ + struct ch *c = &k->ch[n]; + int out = c->carrying ? c->cphase : c->level; + if (out != c->out) { + c->out = out; + esk_esp32_signal(k->s, SIG_OUT0 + n, out, 1); + } +} + +static u32 duty_half(const struct ch *c, int high) +{ + u32 v = high ? reg(c->duty) >> 16 : reg(c->duty) & 0xffffu; + return v ? v : 1u; +} + +/* the carrier for the output's present level: on (from its high half) or off */ +static void tx_carrier(struct e32_rmt *k, int n) +{ + struct ch *c = &k->ch[n]; + u32 c0 = reg(c->conf0); + int want = (c0 & CARRIER_EN) && c->busy && c->level == ((c0 & CARRIER_LV) != 0) && src_fs(k, c) > 0; + if (want && !c->carrying) { + c->carrying = 1; + c->cphase = 1; + c->cleft = fs_of((double)duty_half(c, 1) * src_fs(k, c)); + } else if (!want) { + c->carrying = 0; + } +} + +static void tx_idle(struct e32_rmt *k, int n, int marker_level) +{ + struct ch *c = &k->ch[n]; + u32 c1 = reg(c->conf1); + c->busy = 0; + c->level = (c1 & IDLE_EN) ? (c1 & IDLE_LV) != 0 : marker_level; + tx_carrier(k, n); + tx_drive(k, n); + status(k, n); +} + +/* the entry at c->e, after the one before ended: its level and period, or + * what an end marker or the memory's end does */ +static void tx_load(struct e32_rmt *k, int n) +{ + struct ch *c = &k->ch[n]; + u32 total = 2u * (u32)BLOCK * mem_size(c); + int restarts = 0; + for (;;) { + u32 v, period; + if (c->e >= total) { + if ((reg(k->apb_conf) & TX_WRAP) || (reg(c->conf1) & TX_CONTI)) { + c->e = 0; + } else { + raise(k, I_ERR(n)); + tx_idle(k, n, c->level); + return; + } + } + v = entry(k, n, c->e); + period = v & 0x7fffu; + if (period) { + c->level = (v >> 15) & 1u; + c->left = fs_of((double)period * tick_fs(k, c)); + if (!c->left) + c->left = 1; + tx_carrier(k, n); + tx_drive(k, n); + status(k, n); + return; + } + /* an end marker */ + if (!(reg(c->conf1) & TX_CONTI)) { + raise(k, I_TX_END(n)); + tx_idle(k, n, (int)((v >> 15) & 1u)); + return; + } + if (c->e == 0 || ++restarts > 1) { /* nothing but markers: nothing to send */ + raise(k, I_ERR(n)); + tx_idle(k, n, c->level); + return; + } + c->e = 0; + } +} + +static void tx_start(struct e32_rmt *k, int n) +{ + struct ch *c = &k->ch[n]; + if (src_fs(k, c) <= 0) + return; + c->busy = 1; + c->e = 0; + c->sent = 0; + c->carrying = 0; + tx_load(k, n); +} + +/* the entry ended: on to the next */ +static void tx_next(struct e32_rmt *k, int n) +{ + struct ch *c = &k->ch[n]; + c->e++; + if (!(c->e & 1u)) { /* a word sent */ + u32 lim = reg(c->lim) & 0x1ffu; + if (lim && ++c->sent >= lim) { + c->sent = 0; + raise(k, I_TX_THR(n)); + } + } + tx_load(k, n); +} + +/* ---- receiving ---------------------------------------------------------------- */ + +/* one entry into the memory: level and period */ +static void rx_write(struct e32_rmt *k, int n, int level, u32 period) +{ + struct ch *c = &k->ch[n]; + u32 total = 2u * (u32)BLOCK * mem_size(c), w; + if (c->full) + return; + if (!(reg(c->conf1) & MEM_OWNER)) { /* the CPU's: MEM_OWNER_ERR */ + status(k, n); + return; + } + if (c->wr >= total) { + c->full = 1; + c->active = 0; + raise(k, I_ERR(n)); + status(k, n); + return; + } + w = (u32)BLOCK * (u32)n + c->wr / 2u; + if (w < NWORDS) { + struct esk_rf_reg *cell = k->ram[w]; + u32 half = (period & 0x7fffu) | (u32)(level != 0) << 15; + esk_rf_hw(cell, c->wr & 1u ? (reg(cell) & 0xffffu) | half << 16 : (reg(cell) & 0xffff0000u) | half); + } + c->wr++; + status(k, n); +} + +static u32 rx_ticks(const struct e32_rmt *k, const struct ch *c, u64 cycles) +{ + double tf = tick_fs(k, c), t; + if (tf <= 0) + return 0; + t = (double)cycles * (double)fs_per_cycle(k) / tf + 0.5; + return t > 32767.0 ? 32767u : (u32)t; +} + +/* the receiver's idle threshold in core cycles */ +static u64 rx_idle_cycles(const struct e32_rmt *k, const struct ch *c) +{ + u64 fpc = fs_per_cycle(k); + double thres = (double)((reg(c->conf0) >> 8) & 0xffffu); + if (!fpc) + return 0; + return (u64)(thres * tick_fs(k, c) / (double)fpc) + 1u; +} + +/* a pulse from `from` to `to` (cycles) the filter drops */ +static int rx_drops(const struct e32_rmt *k, const struct ch *c, u64 from, u64 to) +{ + u32 apb = esk_esp32_apb_hz(k->s); + double len = (double)(to - from) * (double)fs_per_cycle(k); + return (reg(c->conf1) & FILTER_EN) && apb && + len < (double)((reg(c->conf1) >> 8) & 0xffu) * (double)FS_PER_S / (double)apb; +} + +static void rx_end(struct e32_rmt *k, int n) +{ + struct ch *c = &k->ch[n]; + if (c->p_level != c->c_level) { /* the last change held: it counts */ + rx_write(k, n, c->c_level, rx_ticks(k, c, c->p_t - c->c_t)); + c->c_level = c->p_level; + c->c_t = c->p_t; + } + rx_write(k, n, c->c_level, 0); + c->active = 0; + raise(k, I_RX_END(n)); + status(k, n); +} + +static void clocking(struct e32_rmt *k); + +/* the inputs changed: each receiver's edge at the time the change came */ +static void rx_watch(void *ctx) +{ + struct e32_rmt *k = (struct e32_rmt *)ctx; + struct esk_mach *s = k->s; + u64 lag = (u64)s->input_lead + s->output_lag, t = s->cycles > lag ? s->cycles - lag : 0; + int n; + for (n = 0; n < NCH; n++) { + struct ch *c = &k->ch[n]; + int level; + if (!c->on) + continue; + level = esk_esp32_signal_in(s, SIG_IN0 + n); + if (level == c->raw) + continue; + c->raw = level; + if (c->full) + continue; + if (!c->active) { /* the first edge starts it */ + c->active = 1; + c->c_level = c->p_level = level; + c->c_t = c->p_t = t; + status(k, n); + continue; + } + if (c->p_level != c->c_level) { + if (rx_drops(k, c, c->p_t, t)) { /* back as it was */ + c->p_level = c->c_level; + continue; + } + rx_write(k, n, c->c_level, rx_ticks(k, c, c->p_t - c->c_t)); + if (!c->active) + continue; + c->c_level = c->p_level; + c->c_t = c->p_t; + } + c->p_level = level; + c->p_t = t; + } + clocking(k); +} + +/* ---- time --------------------------------------------------------------------- */ + +static void clocking(struct e32_rmt *k) +{ + int n, on = 0; + for (n = 0; n < NCH; n++) + if (k->ch[n].busy || k->ch[n].carrying || k->ch[n].active) + on = 1; + esk_rf_clock(k->p, &k->clocked, on); +} + +/* fs until the channel's next edge, or 0 for none */ +static u64 tx_due(const struct ch *c) +{ + u64 d = 0; + if (c->busy) + d = c->left; + if (c->carrying && (!d || c->cleft < d)) + d = c->cleft; + return d; +} + +static void rmt_tick(void *ctx, u32 cycles) +{ + struct e32_rmt *k = (struct e32_rmt *)ctx; + struct esk_mach *s = k->s; + u64 fpc = fs_per_cycle(k), budget = (u64)cycles * fpc; + int n; + if (!fpc) + return; + for (;;) { + u64 d = 0; + for (n = 0; n < NCH; n++) { + u64 x = tx_due(&k->ch[n]); + if (x && (!d || x < d)) + d = x; + } + if (!d || d > budget) { + for (n = 0; n < NCH; n++) { + struct ch *c = &k->ch[n]; + if (c->busy) + c->left -= budget < c->left ? budget : c->left; + if (c->carrying) + c->cleft -= budget < c->cleft ? budget : c->cleft; + } + break; + } + budget -= d; + /* the edges switch at their time: this many cycles before now */ + s->output_lag = (u32)(budget / fpc); + for (n = 0; n < NCH; n++) { + struct ch *c = &k->ch[n]; + int ended = c->busy && c->left == d; + if (c->busy) + c->left -= d; + if (c->carrying) { + c->cleft -= d; + if (!c->cleft) { + c->cphase = !c->cphase; + c->cleft = fs_of((double)duty_half(c, c->cphase) * src_fs(k, c)); + if (!c->cleft) + c->cleft = 1; + if (!ended) + tx_drive(k, n); + } + } + if (ended) + tx_next(k, n); + } + s->output_lag = 0; + } + for (n = 0; n < NCH; n++) { + struct ch *c = &k->ch[n]; + u64 last = c->p_t > c->c_t ? c->p_t : c->c_t; + if (c->active && s->cycles >= last + rx_idle_cycles(k, c)) + rx_end(k, n); + } + clocking(k); +} + +static int rmt_next(void *ctx, u32 *cycles) +{ + struct e32_rmt *k = (struct e32_rmt *)ctx; + u64 fpc = fs_per_cycle(k), best = 0; + int n, any = 0; + if (!fpc) + return 0; + for (n = 0; n < NCH; n++) { + struct ch *c = &k->ch[n]; + u64 d = tx_due(c); + if (d) { + u64 x = (d + fpc - 1u) / fpc; + if (!any || x < best) + best = x; + any = 1; + } + if (c->active) { + u64 last = c->p_t > c->c_t ? c->p_t : c->c_t, at = last + rx_idle_cycles(k, c); + at = at > k->s->cycles ? at - k->s->cycles : 1u; + if (!any || at < best) + best = at; + any = 1; + } + } + if (!any) + return 0; + *cycles = best == 0 ? 1u : best > 0xffffffffu ? 0xffffffffu : (u32)best; + return 1; +} + +/* ---- registers ---------------------------------------------------------------- */ + +static u32 rmt_read(void *ctx, u32 off, int n) +{ + struct e32_rmt *k = (struct e32_rmt *)ctx; + u32 w = off & ~3u; + if (w < 0x20u && !(reg(k->apb_conf) & FIFO_MASK)) { /* CHnDATA: the channel's next word */ + int i = (int)(w / 4u); + struct ch *c = &k->ch[i]; + u32 at = (u32)BLOCK * (u32)i + c->apb, v = at < NWORDS ? reg(k->ram[at]) : 0; + if (!k->s->bus.debug) { + c->apb = (c->apb + 1u) % ((u32)BLOCK * mem_size(c)); + status(k, i); + } + return v >> (8u * (off & 3u)); + } + return esk_rf_read(k->p, off, n); +} + +static void conf1_write(struct e32_rmt *k, int n, u32 v, u32 was) +{ + struct ch *c = &k->ch[n]; + if (v & MEM_RD_RST) + c->e = 0; + if (v & APB_MEM_RST) + c->apb = 0; + if (v & MEM_WR_RST) { + c->wr = 0; + c->full = 0; + } + if ((v & RX_EN) && !(was & RX_EN)) { + c->on = 1; + c->active = 0; + c->full = 0; + c->raw = esk_esp32_signal_in(k->s, SIG_IN0 + n); + } else if (!(v & RX_EN)) { + c->on = 0; + c->active = 0; + } + if (!c->busy) /* the idle output as now configured */ + tx_idle(k, n, c->level); + if (v & TX_START) { + esk_rf_hw(c->conf1, reg(c->conf1) & ~TX_START); /* it clears itself */ + tx_start(k, n); + } + status(k, n); +} + +static void rmt_write(void *ctx, u32 off, int n, u32 v) +{ + struct e32_rmt *k = (struct e32_rmt *)ctx; + u32 w = off & ~3u; + int i; + if (w < 0x20u) { /* CHnDATA */ + if (!(reg(k->apb_conf) & FIFO_MASK)) { + struct ch *c = &k->ch[w / 4u]; + u32 at = (u32)BLOCK * (w / 4u) + c->apb; + if (at < NWORDS) + esk_rf_hw(k->ram[at], v); + c->apb = (c->apb + 1u) % ((u32)BLOCK * mem_size(c)); + status(k, (int)(w / 4u)); + } + return; + } + if (w == 0xACu) { /* INT_CLR */ + esk_rf_hw(k->raw, reg(k->raw) & ~v); + update(k); + return; + } + if ((w >= 0x60u && w < 0xA8u)) + return; /* STATUS, ADDR, INT_RAW and INT_ST are read-only */ + { + u32 was[NCH]; + for (i = 0; i < NCH; i++) + was[i] = reg(k->ch[i].conf1); + esk_rf_write(k->p, off, n, v); + if (w == 0xA8u) /* INT_ENA */ + update(k); + for (i = 0; i < NCH; i++) + if (w == esk_rf_off(k->ch[i].conf1)) + conf1_write(k, i, reg(k->ch[i].conf1), was[i]); + } + clocking(k); +} + +static void rmt_reset(void *ctx) +{ + struct e32_rmt *k = (struct e32_rmt *)ctx; + int i; + for (i = 0; i < NCH; i++) { + struct ch *c = &k->ch[i]; + struct ch keep = *c; + memset(c, 0, sizeof *c); + c->conf0 = keep.conf0; + c->conf1 = keep.conf1; + c->status = keep.status; + c->addr = keep.addr; + c->duty = keep.duty; + c->lim = keep.lim; + c->out = -1; + } + k->level = 0; + esk_esp32_source(k->s, SOURCE, 0); + k->clocked = 0; + for (i = 0; i < NCH; i++) { + tx_idle(k, i, 0); + k->ch[i].raw = esk_esp32_signal_in(k->s, SIG_IN0 + i); + } + update(k); + clocking(k); +} + +const struct esk_dev_ops esk_esp32_rmt_ops = { + "esp32-rmt", rmt_read, rmt_write, rmt_reset, rmt_tick, rmt_next, +}; + +void *esk_esp32_rmt_create(struct esk_mach *s, struct esk_rf_periph *p) +{ + struct e32_rmt *k = (struct e32_rmt *)esk_mach_alloc(s, sizeof *k); + char name[24]; + int i; + if (!k) + return 0; + memset(k, 0, sizeof *k); + k->s = s; + k->p = p; + k->apb_conf = esk_rf_reg(p, "APB_CONF"); + k->raw = esk_rf_reg(p, "INT_RAW"); + k->st = esk_rf_reg(p, "INT_ST"); + k->ena = esk_rf_reg(p, "INT_ENA"); + if (!k->apb_conf || !k->raw || !k->st || !k->ena) + return 0; + for (i = 0; i < NCH; i++) { + struct ch *c = &k->ch[i]; + snprintf(name, sizeof name, "CH%dCONF0", i); + c->conf0 = esk_rf_reg(p, name); + snprintf(name, sizeof name, "CH%dCONF1", i); + c->conf1 = esk_rf_reg(p, name); + snprintf(name, sizeof name, "CH%dSTATUS", i); + c->status = esk_rf_reg(p, name); + snprintf(name, sizeof name, "CH%dADDR", i); + c->addr = esk_rf_reg(p, name); + snprintf(name, sizeof name, "CH%dCARRIER_DUTY", i); + c->duty = esk_rf_reg(p, name); + snprintf(name, sizeof name, "CH%d_TX_LIM", i); + c->lim = esk_rf_reg(p, name); + if (!c->conf0 || !c->conf1 || !c->status || !c->addr || !c->duty || !c->lim) + return 0; + } + for (i = 0; i < NWORDS; i++) { + snprintf(name, sizeof name, "RAM%d", i); + if (!(k->ram[i] = esk_rf_reg(p, name))) + return 0; + } + if (esk_esp32_signal_watch(s, rx_watch, k) != 0) + return 0; + return k; +} diff --git a/core/mcu/models.c b/core/mcu/models.c index 52c983242..0f603f9be 100644 --- a/core/mcu/models.c +++ b/core/mcu/models.c @@ -73,6 +73,7 @@ static const struct esk_rf_model_type model_types[] = { { "esp32-sha", &esk_esp32_sha_ops, esk_esp32_sha_create }, { "esp32-ledc", &esk_esp32_ledc_ops, esk_esp32_ledc_create }, { "esp32-i2c", &esk_esp32_i2c_ops, esk_esp32_i2c_create }, + { "esp32-rmt", &esk_esp32_rmt_ops, esk_esp32_rmt_create }, { "esp32-iomux", &esk_esp32_iomux_ops, esk_esp32_iomux_create }, { "esp32c3-intmtx", &esk_esp32c3_intmtx_ops, esk_esp32c3_intmtx_create }, { "esp32c3-uart", &esk_esp32c3_uart_ops, esk_esp32c3_uart_create }, diff --git a/docs/STATUS.md b/docs/STATUS.md index 37798483c..41b396e1c 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -17,9 +17,10 @@ and through `esp_restart` (rst:0xc SW_CPU_RESET). Evidence: `test_lx6` scenarios `esp32_hello_world` (9/9), `esp32_blink` (6/6), `esp32_ledc_basic` (2/2), `esp32_i2c_eeprom` (3/3, I2C0 writing and reading a 24LC256 at 400 kHz), `esp32_gptimer` (17/17), `esp32_uart_echo` -(6/6) and `esp32_real_time_stats` (10/10). Not yet: Wi-Fi and Bluetooth, the +(6/6), `esp32_real_time_stats` (10/10) and `esp32_blink_strip` (7/7, a +WS2812B through the RMT). Not yet: Wi-Fi and Bluetooth, the double-precision accelerator's instructions, the peripherals the -description's fidelity lists (I2S, SPI2/3, RMT, ADC, ...), +description's fidelity lists (I2S, SPI2/3, ADC, ...), sleep. The standalone harness below remains as its own qualification fixtures. diff --git a/docs/coverage.md b/docs/coverage.md index 99d40d33f..8245e8da3 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -8,7 +8,7 @@ evidence for each model is in [STATUS.md](STATUS.md). | MCU | Modelled | Registers only | Missing | Peripherals | |---|---|---|---|---| -| ESP32 | 17 | 0 | 28 | 45 | +| ESP32 | 18 | 0 | 27 | 45 | | ESP32-C3 | 35 | 0 | 0 | 35 | | ATmega328P | 15 | 0 | 0 | 15 | | PIC24FJ1024GB610 | 11 | 4 | 17 | 32 | @@ -23,7 +23,7 @@ evidence for each model is in [STATUS.md](STATUS.md). ## ESP32 (`espressif/esp32`) Boards: ESP32-DevKitC V4. -17 of 45 peripherals modelled, 0 registers only, 28 missing. +18 of 45 peripherals modelled, 0 registers only, 27 missing. | Peripheral | Model | State | |---|---|---| @@ -50,7 +50,7 @@ Boards: ESP32-DevKitC V4. | I2C0 | `esp32-i2c` | modelled | | UHCI0 | — | **missing** | | SLCHOST | — | **missing** | -| RMT | — | **missing** | +| RMT | `esp32-rmt` | modelled | | PCNT | — | **missing** | | SLC | — | **missing** | | LEDC | `esp32-ledc` | modelled | @@ -73,7 +73,7 @@ Boards: ESP32-DevKitC V4. | I2S1 | — | **missing** | | UART2 | `esp32-uart` | modelled | -Declared omissions: Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written); the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept; PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts; I2C: the non-FIFO mode (NONFIFO_EN, RX_REC_FULL, TX_SEND_EMPTY), SAMPLE_SCL_LEVEL, the input filters (only their share of the SCL high time), 10-bit addressing's R/W check; the peripherals without a model yet: I2S0/1, SPI2/3 (HSPI, VSPI), RMT, MCPWM, PCNT, TWAI, SDMMC, EMAC, the SAR ADCs, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption; sleep modes (light and deep sleep, the power domains), brown-out; the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection. +Declared omissions: Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written); the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept; PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts; RMT: the memory's power-down (MEM_PD), STATUS's STATE and the APB-side error bits, the carrier on an idle output; I2C: the non-FIFO mode (NONFIFO_EN, RX_REC_FULL, TX_SEND_EMPTY), SAMPLE_SCL_LEVEL, the input filters (only their share of the SCL high time), 10-bit addressing's R/W check; the peripherals without a model yet: I2S0/1, SPI2/3 (HSPI, VSPI), MCPWM, PCNT, TWAI, SDMMC, EMAC, the SAR ADCs, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption; sleep modes (light and deep sleep, the power domains), brown-out; the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection. ## ESP32-C3 (`espressif/esp32c3`) diff --git a/tests/vendor/esp-idf/examples/esp32_blink_strip/project.json b/tests/vendor/esp-idf/examples/esp32_blink_strip/project.json new file mode 100644 index 000000000..f56d3b192 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_blink_strip/project.json @@ -0,0 +1,100 @@ +{ + "schema_version": 1, + "kind": "project", + "name": "esp-idf-esp32_blink_strip", + "description": "ESP-IDF's get-started/blink with its LED-strip option on the ESP32-DevKitC V4 (sdkconfig.defaults: CONFIG_BLINK_LED_STRIP, the RMT backend, GPIO18): a WS2812B on IO18, powered from the board's 5 V through a 1N4007, a common way to drive one from 3.3 V logic: the diode's drop at the LED's supply current lowers VDD to about 4.2 V, and with it the input's high threshold (0.7 VDD) below the ESP32's 3.3 V. The part's model draws no supply current (its fidelity says so), so here the diode carries none and LED_VDD reads the board's 5 V net.", + "policy": "electrical", + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc", + "layout": { + "x": 360, + "y": 120 + }, + "firmware": { + "image": "flash.bin", + "format": "bin" + } + } + ], + "components": [ + { + "id": "USB", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5 + }, + "layout": { + "x": 120, + "y": 160 + } + }, + { + "id": "GND1", + "component": "emblink/gnd", + "layout": { + "x": 120, + "y": 320 + } + }, + { + "id": "D1", + "component": "emblink/diode_1n4007", + "layout": { + "x": 120, + "y": 460 + } + }, + { + "id": "LED1", + "component": "emblink/ws2812b", + "layout": { + "x": 120, + "y": 560 + } + } + ], + "nets": { + "GND": [ + "USB.-", + "GND1.GND", + "B1.GND1", + "LED1.GND" + ], + "USB5V": [ + "USB.+", + "B1.VBUS" + ], + "5V": [ + "B1.5V", + "D1.A" + ], + "LED_VDD": [ + "D1.K", + "LED1.VDD" + ], + "DATA": [ + "B1.IO18", + "LED1.DIN" + ] + }, + "probes": [ + { + "id": "console", + "target": "serial:B1", + "kind": "serial" + }, + { + "id": "led", + "target": "output:LED1.color", + "kind": "value" + }, + { + "id": "frames", + "target": "output:LED1.frames", + "kind": "value" + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/examples/esp32_blink_strip/scenario.json b/tests/vendor/esp-idf/examples/esp32_blink_strip/scenario.json new file mode 100644 index 000000000..89de6eef0 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_blink_strip/scenario.json @@ -0,0 +1,66 @@ +{ + "schema_version": 1, + "kind": "scenario", + "name": "esp32_blink_strip", + "until": "3900ms", + "description": "blink with the LED strip on the ESP32: the RMT (channel 0, 10 MHz from APB_CLK, through the matrix's rmt_sig_out0) sends the WS2812B white at 16/255 for one second, then black, and again; the console says each. Five frames by 3.8 s: the clear when the strip is configured, then OFF, ON, OFF, ON.", + "inputs": [], + "assertions": [ + { + "id": "strip", + "at": "1s", + "probe": "console", + "expect": { + "contains": "Example configured to blink addressable LED!" + } + }, + { + "id": "off-first", + "at": "1s", + "probe": "led", + "expect": { + "equals": "#000000" + } + }, + { + "id": "on", + "at": "1800ms", + "probe": "led", + "expect": { + "equals": "#101010" + } + }, + { + "id": "off", + "at": "2800ms", + "probe": "led", + "expect": { + "equals": "#000000" + } + }, + { + "id": "on-again", + "at": "3800ms", + "probe": "led", + "expect": { + "equals": "#101010" + } + }, + { + "id": "frames", + "at": "3800ms", + "probe": "frames", + "expect": { + "equals": 5 + } + }, + { + "id": "said", + "at": "3800ms", + "probe": "console", + "expect": { + "contains": "Turning the LED ON!" + } + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/examples/esp32_blink_strip/sdkconfig.defaults b/tests/vendor/esp-idf/examples/esp32_blink_strip/sdkconfig.defaults new file mode 100644 index 000000000..4557fd979 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_blink_strip/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_BLINK_LED_STRIP=y +CONFIG_BLINK_LED_STRIP_BACKEND_RMT=y +CONFIG_BLINK_GPIO=18 diff --git a/tests/vendor/esp-idf/run.py b/tests/vendor/esp-idf/run.py index 63712fede..4c8d9c818 100644 --- a/tests/vendor/esp-idf/run.py +++ b/tests/vendor/esp-idf/run.py @@ -78,6 +78,7 @@ "esp32_gptimer": "examples/peripherals/timer_group/gptimer", "esp32_uart_echo": "examples/peripherals/uart/uart_echo", "esp32_real_time_stats": "examples/system/freertos/real_time_stats", + "esp32_blink_strip": "examples/get-started/blink", } # the target an example is built for, when not the ESP32-C3 (its board in # examples//project.json), and the toolchain that target needs @@ -89,6 +90,7 @@ "esp32_gptimer": "esp32", "esp32_uart_echo": "esp32", "esp32_real_time_stats": "esp32", + "esp32_blink_strip": "esp32", } TOOLCHAINS = {"esp32c3": "riscv32-esp-elf-gcc", "esp32": "xtensa-esp32-elf-gcc"} # examples built only for another's project (with examples//'s From 9d2d90415f40adee89885cd8977f419880a82731 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Sun, 8 Nov 2026 04:57:00 +0000 Subject: [PATCH 53/57] ESP32: the SAR ADCs, with ESP-IDF's adc oneshot_read SENS's RTC controller's one-shot conversions on ADC1 (GPIO36-39, 32-35) and ADC2 (GPIO4, 0, 2, 15, 13, 12, 14, 27, 25, 26): MEAS1/2_START_SAR, the attenuation per channel, 9 to 12 bits, the converter's inverted output and DATA_INV, DONE after the conversion's SAR clocks. The transfer is the line ESP-IDF's Vref calibration fits for the chip, with the chip's eFuse Vref, so the calibrated voltage matches the pin's. ESP-IDF's adc/oneshot_read runs unmodified on the DevKitC with three sources (esp32_adc_oneshot_read 6/6: 1118 for 1.1 V, 2402 for 2.2 V, 433 for 0.5 V on ADC2). --- CHANGELOG.md | 6 +- catalog/mcus/espressif/esp32/mcu.json | 19 +- core/mcu/esp32/soc/adc.c | 204 ++++++++++++++++++ core/mcu/esp32/soc/esp32.h | 3 + core/mcu/models.c | 1 + docs/STATUS.md | 7 +- docs/coverage.md | 8 +- .../esp32_adc_oneshot_read/project.json | 112 ++++++++++ .../esp32_adc_oneshot_read/scenario.json | 58 +++++ tests/vendor/esp-idf/run.py | 2 + 10 files changed, 405 insertions(+), 15 deletions(-) create mode 100644 core/mcu/esp32/soc/adc.c create mode 100644 tests/vendor/esp-idf/examples/esp32_adc_oneshot_read/project.json create mode 100644 tests/vendor/esp-idf/examples/esp32_adc_oneshot_read/scenario.json diff --git a/CHANGELOG.md b/CHANGELOG.md index d4a82d03a..feef4881f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,8 @@ versions follow semantic versioning. There is no release yet. interrupt matrix), RTC_CNTL, eFuse, APB_CTRL clocks, GPIO/IO_MUX with strapping, UART0-2, TIMG0/1, SPI0/1 with the GT25Q32A flash, SHA, LEDC, I2C0/1 (master and slave, 16 commands, APB_CLK timing; DPORT's - PERIP_RST_EN resets them), the RMT's eight channels, the internal analog I2C, RNG and the AHB view. + PERIP_RST_EN resets them), the RMT's eight channels, the SAR ADCs + through SENS, the internal analog I2C, RNG and the AHB view. - Added the `emblink/esp32_devkitc` board (ESP32-DevKitC V4, WROOM-32E) with its power path, LED, Boot and EN buttons, headers and fixtures. - Espressif's ROM, ESP-IDF v5.4's bootloader and hello_world run unmodified @@ -29,7 +30,8 @@ versions follow semantic versioning. There is no release yet. `esp32_blink` 6/6, `esp32_ledc_basic` 2/2, `esp32_i2c_eeprom` 3/3 on a 24LC256, `esp32_gptimer` 17/17, `esp32_uart_echo` 6/6 on UART2, `esp32_real_time_stats` 10/10 across both cores, - `esp32_blink_strip` 7/7 on a WS2812B through the RMT; the UARTs keep + `esp32_blink_strip` 7/7 on a WS2812B through the RMT, + `esp32_adc_oneshot_read` 6/6 with the eFuse Vref's calibration; the UARTs keep MEM_RX_STATUS's addresses, from which ESP-IDF counts the bytes waiting); `test_lx6` checks the core with programs from Espressif's assembler. ### Original ESP32 digital per-pin hold diff --git a/catalog/mcus/espressif/esp32/mcu.json b/catalog/mcus/espressif/esp32/mcu.json index 587b267dc..b97f0ba30 100644 --- a/catalog/mcus/espressif/esp32/mcu.json +++ b/catalog/mcus/espressif/esp32/mcu.json @@ -203,6 +203,10 @@ { "match": "RMT", "model": "esp32-rmt" + }, + { + "match": "SENS", + "model": "esp32-sens" } ], "io": { @@ -271,14 +275,15 @@ }, "fidelity": { "functional": { - "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts, MEM_RX_STATUS's addresses that ESP-IDF counts the bytes waiting from), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), LEDC (eight high-speed and eight low-speed PWM channels on eight timers, fading, on the matrix's ledc_hs/ls_sig_out), I2C0 and I2C1 (master and slave on the open-drain lines through the matrix, the 16 commands with RSTART, WRITE, READ, STOP and END, the bus timing in APB_CLK cycles, ACK checks, arbitration, the SCL timeout, the 32-byte FIFOs through DATA and its AHB view, 7- and 10-bit slave addresses; ESP-IDF's i2c_eeprom runs on a 24LC256), the RMT (eight channels sending or receiving through the matrix, 64-word blocks of the 512-word RAM, end markers, continuous mode, wrap, TX_LIM's threshold, the carrier, the receiver's idle threshold and filter, the RAM through CHnDATA; ESP-IDF's led_strip lights a WS2812B), PERIP_RST_EN's resets of I2C0, I2C1 and LEDC, the internal analog I2C master, the random number generator and the peripherals' AHB view. The evidence: test_lx6.c's programs assembled by Espressif's assembler (integer, shifts, multiply and divide, loops, branches, memory with S32C1I, windowed recursion through the ROM's own window vectors, SYSCALL, the CCOMPARE timer interrupt, FPU, MAC16) and the APP_CPU's ROM data kept apart; ESP-IDF v5.4's hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo, real_time_stats and blink with its LED strip built unmodified for the esp32 target on the ESP32-DevKitC V4, from the ROM's banner to esp_restart; the ROM image equals Espressif QEMU's esp32-v3-rom.bin byte for byte, and the APP_CPU's view of it QEMU's esp32-v3-rom-app.bin.", + "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts, MEM_RX_STATUS's addresses that ESP-IDF counts the bytes waiting from), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), LEDC (eight high-speed and eight low-speed PWM channels on eight timers, fading, on the matrix's ledc_hs/ls_sig_out), I2C0 and I2C1 (master and slave on the open-drain lines through the matrix, the 16 commands with RSTART, WRITE, READ, STOP and END, the bus timing in APB_CLK cycles, ACK checks, arbitration, the SCL timeout, the 32-byte FIFOs through DATA and its AHB view, 7- and 10-bit slave addresses; ESP-IDF's i2c_eeprom runs on a 24LC256), the RMT (eight channels sending or receiving through the matrix, 64-word blocks of the 512-word RAM, end markers, continuous mode, wrap, TX_LIM's threshold, the carrier, the receiver's idle threshold and filter, the RAM through CHnDATA; ESP-IDF's led_strip lights a WS2812B), the SAR ADCs as SENS's RTC controller drives them (one conversion at a time on ADC1's eight and ADC2's ten pads, the attenuations, 9 to 12 bits, the converter's inverted output and DATA_INV, the conversion's time; the transfer the line ESP-IDF's Vref calibration fits for the chip, its eFuse Vref), PERIP_RST_EN's resets of I2C0, I2C1 and LEDC, the internal analog I2C master, the random number generator and the peripherals' AHB view. The evidence: test_lx6.c's programs assembled by Espressif's assembler (integer, shifts, multiply and divide, loops, branches, memory with S32C1I, windowed recursion through the ROM's own window vectors, SYSCALL, the CCOMPARE timer interrupt, FPU, MAC16) and the APP_CPU's ROM data kept apart; ESP-IDF v5.4's hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo, real_time_stats, blink with its LED strip and adc oneshot_read built unmodified for the esp32 target on the ESP32-DevKitC V4, from the ROM's banner to esp_restart; the ROM image equals Espressif QEMU's esp32-v3-rom.bin byte for byte, and the APP_CPU's view of it QEMU's esp32-v3-rom-app.bin.", "omissions": [ "Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written)", "the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept", "PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts", "RMT: the memory's power-down (MEM_PD), STATUS's STATE and the APB-side error bits, the carrier on an idle output", + "the SAR ADCs: the nonlinearity above about 2450 mV at 11 dB and a chip's spread from the fitted line, noise, the ULP's and the digital controller's (DMA, I2S) conversions, the Hall sensor, ADC2's arbitration with Wi-Fi, the RTC IO MUX's routing of a pad to the ADC (not checked)", "I2C: the non-FIFO mode (NONFIFO_EN, RX_REC_FULL, TX_SEND_EMPTY), SAMPLE_SCL_LEVEL, the input filters (only their share of the SCL high time), 10-bit addressing's R/W check", - "the peripherals without a model yet: I2S0/1, SPI2/3 (HSPI, VSPI), MCPWM, PCNT, TWAI, SDMMC, EMAC, the SAR ADCs, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption", + "the peripherals without a model yet: I2S0/1, SPI2/3 (HSPI, VSPI), MCPWM, PCNT, TWAI, SDMMC, EMAC, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption", "sleep modes (light and deep sleep, the power domains), brown-out", "the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection" ], @@ -292,7 +297,8 @@ "repo:tests/vendor/esp-idf/examples/esp32_gptimer/scenario.json", "repo:tests/vendor/esp-idf/examples/esp32_uart_echo/scenario.json", "repo:tests/vendor/esp-idf/examples/esp32_real_time_stats/scenario.json", - "repo:tests/vendor/esp-idf/examples/esp32_blink_strip/scenario.json" + "repo:tests/vendor/esp-idf/examples/esp32_blink_strip/scenario.json", + "repo:tests/vendor/esp-idf/examples/esp32_adc_oneshot_read/scenario.json" ] }, "timed": { @@ -398,18 +404,19 @@ }, "i18n": { "fr": { - "source": "f8310c3fa74c", + "source": "bff3ddeda639", "description": "Espressif ESP32 (ESP32-D0WD-V3, révision de puce v3.1) : deux cœurs Xtensa LX6 jusqu'à 240 MHz, 520 Kio de SRAM, 448 Kio de ROM, 16 Kio de mémoire RTC, une flash SPI externe derrière les caches des cœurs, Wi-Fi et Bluetooth ; le MCU de l'ESP32-DevKitC. La ROM d'Espressif, le bootloader de second niveau d'ESP-IDF et les applications ESP-IDF tournent sur les deux cœurs ; les radios ne sont pas encore modélisées.", "fidelity": { "functional": { - "claims": "Les deux cœurs Xtensa LX6 (mcu/xtensa/lx6.c, lx6fp.c) tels que les configure le core-isa.h d'Espressif : les instructions de base et de densité, les registres fenêtrés (64, le contrôle de débordement de chaque instruction, CALL4/8/12, ENTRY, RETW et son sous-débordement, MOVSP, ROTW, L32E/S32E), les boucles sans surcoût, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, les booléens, S32C1I avec SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), le paquet MAC16, la FPU simple précision (modes d'arrondi et drapeaux IEEE ; les séquences de division et de racine carrée qu'exécute libgcc donnent des résultats correctement arrondis), les exceptions XEA2 avec vecteurs relocalisables, 32 interruptions aux niveaux et types de core-isa.h (niveau, front, logicielles, les temporisateurs CCOMPARE0-2 contre CCOUNT, NMI), RSIL, WAITI, RFI, BREAK, IBREAK, DBREAK et ICOUNT de l'option de débogage, la protection par régions (huit régions de 512 Mo, 15 sans accès), les chargements et écritures non alignés, et les accès de données en 32 bits seulement du bus d'instructions. Les deux cœurs partagent la machine, l'APP_CPU maintenu en reset jusqu'à ce que DPORT le libère et voyant la ROM avec ses pointeurs vers les données ROM du PRO_CPU déplacés vers les siennes, comme dans l'image ROM APP_CPU d'Espressif. Le démarrage : la ROM d'Espressif (esp32_rev300_rom.elf, révision v3) depuis le vecteur de reset, sa bannière, les broches de configuration (GPIO_STRAP : SPI_FAST_FLASH_BOOT, ou le mode téléchargement avec GPIO0 bas), les causes de reset (RTC_CNTL), le contrôle anti-glitch de l'eFuse, la lecture de la flash par SPI1, le bootloader de second niveau d'ESP-IDF et sa vérification d'image sur l'accélérateur SHA, la MMU flash des caches, et l'application sur les deux cœurs. Les modèles : DPORT (reset, horloge et blocage de l'APP_CPU, CPU_PER_CONF, vidage et état des caches, tables de la MMU flash, les 69 sources de la matrice d'interruptions, les interruptions inter-CPU), RTC_CNTL (causes de reset, le temporisateur RTC, le chien de garde RTC avec sa protection au démarrage sur flash, les resets et blocages logiciels, les registres de stockage, SOC_CLK_SEL), APB_CTRL (les horloges CPU et APB depuis XTAL, PLL ou RC_FAST), le contrôleur eFuse avec les bits d'usine de la puce (ESP32-D0WD-V3 v3.1, une MAC avec son CRC, ou celle des réglages de la carte), GPIO et IO_MUX (40 plots, la matrice, tirages, drain ouvert, interruptions sur chaque CPU, le verrou de configuration), UART0-2 (FIFO, trames au débit, interruptions, les adresses de MEM_RX_STATUS d'après lesquelles ESP-IDF compte les octets en attente), TIMG0/1 (T0, T1, le temporisateur LAC, le MWDT avec sa protection au démarrage sur flash, la calibration de RTC_SLOW_CLK), SPI0/SPI1 avec la flash Giantec GT25Q32A du module (C4 60 16, l'identifiant que rapporte la carte du propriétaire), l'accélérateur SHA (SHA-1, -256, -384, -512), LEDC (huit canaux MLI rapides et huit lents sur huit temporisateurs, le fondu, sur ledc_hs/ls_sig_out de la matrice), I2C0 et I2C1 (maître et esclave sur les lignes à drain ouvert à travers la matrice, les 16 commandes avec RSTART, WRITE, READ, STOP et END, le timing du bus en cycles d'APB_CLK, les contrôles d'ACK, l'arbitrage, le délai d'attente sur SCL, les FIFO de 32 octets par DATA et sa vue AHB, les adresses esclaves sur 7 et 10 bits ; l'exemple i2c_eeprom d'ESP-IDF fonctionne avec une 24LC256), le RMT (huit canaux qui émettent ou reçoivent à travers la matrice, des blocs de 64 mots de la RAM de 512 mots, les marqueurs de fin, le mode continu, le rebouclage, le seuil de TX_LIM, la porteuse, le seuil d'inactivité et le filtre du récepteur, la RAM par CHnDATA ; le led_strip d'ESP-IDF allume une WS2812B), les resets de PERIP_RST_EN pour I2C0, I2C1 et LEDC, le maître I2C analogique interne, le générateur de nombres aléatoires et la vue AHB des périphériques. Les preuves : les programmes de test_lx6.c assemblés par l'assembleur d'Espressif (entiers, décalages, multiplication et division, boucles, branchements, mémoire avec S32C1I, récursion fenêtrée à travers les vecteurs de fenêtre de la ROM, SYSCALL, l'interruption du temporisateur CCOMPARE, FPU, MAC16) et les données ROM de l'APP_CPU tenues à part ; hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo, real_time_stats et blink avec son ruban de LED d'ESP-IDF v5.4 compilés sans modification pour la cible esp32 sur l'ESP32-DevKitC V4, de la bannière de la ROM jusqu'à esp_restart ; l'image ROM est identique octet pour octet à l'esp32-v3-rom.bin du QEMU d'Espressif, et la vue qu'en a l'APP_CPU à son esp32-v3-rom-app.bin.", + "claims": "Les deux cœurs Xtensa LX6 (mcu/xtensa/lx6.c, lx6fp.c) tels que les configure le core-isa.h d'Espressif : les instructions de base et de densité, les registres fenêtrés (64, le contrôle de débordement de chaque instruction, CALL4/8/12, ENTRY, RETW et son sous-débordement, MOVSP, ROTW, L32E/S32E), les boucles sans surcoût, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, les booléens, S32C1I avec SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), le paquet MAC16, la FPU simple précision (modes d'arrondi et drapeaux IEEE ; les séquences de division et de racine carrée qu'exécute libgcc donnent des résultats correctement arrondis), les exceptions XEA2 avec vecteurs relocalisables, 32 interruptions aux niveaux et types de core-isa.h (niveau, front, logicielles, les temporisateurs CCOMPARE0-2 contre CCOUNT, NMI), RSIL, WAITI, RFI, BREAK, IBREAK, DBREAK et ICOUNT de l'option de débogage, la protection par régions (huit régions de 512 Mo, 15 sans accès), les chargements et écritures non alignés, et les accès de données en 32 bits seulement du bus d'instructions. Les deux cœurs partagent la machine, l'APP_CPU maintenu en reset jusqu'à ce que DPORT le libère et voyant la ROM avec ses pointeurs vers les données ROM du PRO_CPU déplacés vers les siennes, comme dans l'image ROM APP_CPU d'Espressif. Le démarrage : la ROM d'Espressif (esp32_rev300_rom.elf, révision v3) depuis le vecteur de reset, sa bannière, les broches de configuration (GPIO_STRAP : SPI_FAST_FLASH_BOOT, ou le mode téléchargement avec GPIO0 bas), les causes de reset (RTC_CNTL), le contrôle anti-glitch de l'eFuse, la lecture de la flash par SPI1, le bootloader de second niveau d'ESP-IDF et sa vérification d'image sur l'accélérateur SHA, la MMU flash des caches, et l'application sur les deux cœurs. Les modèles : DPORT (reset, horloge et blocage de l'APP_CPU, CPU_PER_CONF, vidage et état des caches, tables de la MMU flash, les 69 sources de la matrice d'interruptions, les interruptions inter-CPU), RTC_CNTL (causes de reset, le temporisateur RTC, le chien de garde RTC avec sa protection au démarrage sur flash, les resets et blocages logiciels, les registres de stockage, SOC_CLK_SEL), APB_CTRL (les horloges CPU et APB depuis XTAL, PLL ou RC_FAST), le contrôleur eFuse avec les bits d'usine de la puce (ESP32-D0WD-V3 v3.1, une MAC avec son CRC, ou celle des réglages de la carte), GPIO et IO_MUX (40 plots, la matrice, tirages, drain ouvert, interruptions sur chaque CPU, le verrou de configuration), UART0-2 (FIFO, trames au débit, interruptions, les adresses de MEM_RX_STATUS d'après lesquelles ESP-IDF compte les octets en attente), TIMG0/1 (T0, T1, le temporisateur LAC, le MWDT avec sa protection au démarrage sur flash, la calibration de RTC_SLOW_CLK), SPI0/SPI1 avec la flash Giantec GT25Q32A du module (C4 60 16, l'identifiant que rapporte la carte du propriétaire), l'accélérateur SHA (SHA-1, -256, -384, -512), LEDC (huit canaux MLI rapides et huit lents sur huit temporisateurs, le fondu, sur ledc_hs/ls_sig_out de la matrice), I2C0 et I2C1 (maître et esclave sur les lignes à drain ouvert à travers la matrice, les 16 commandes avec RSTART, WRITE, READ, STOP et END, le timing du bus en cycles d'APB_CLK, les contrôles d'ACK, l'arbitrage, le délai d'attente sur SCL, les FIFO de 32 octets par DATA et sa vue AHB, les adresses esclaves sur 7 et 10 bits ; l'exemple i2c_eeprom d'ESP-IDF fonctionne avec une 24LC256), le RMT (huit canaux qui émettent ou reçoivent à travers la matrice, des blocs de 64 mots de la RAM de 512 mots, les marqueurs de fin, le mode continu, le rebouclage, le seuil de TX_LIM, la porteuse, le seuil d'inactivité et le filtre du récepteur, la RAM par CHnDATA ; le led_strip d'ESP-IDF allume une WS2812B), les ADC SAR tels que les pilote le contrôleur RTC de SENS (une conversion à la fois sur les huit plots de l'ADC1 et les dix de l'ADC2, les atténuations, 9 à 12 bits, la sortie inversée du convertisseur et DATA_INV, la durée de la conversion ; la caractéristique est la droite que la calibration Vref d'ESP-IDF ajuste pour la puce, avec son Vref d'eFuse), les resets de PERIP_RST_EN pour I2C0, I2C1 et LEDC, le maître I2C analogique interne, le générateur de nombres aléatoires et la vue AHB des périphériques. Les preuves : les programmes de test_lx6.c assemblés par l'assembleur d'Espressif (entiers, décalages, multiplication et division, boucles, branchements, mémoire avec S32C1I, récursion fenêtrée à travers les vecteurs de fenêtre de la ROM, SYSCALL, l'interruption du temporisateur CCOMPARE, FPU, MAC16) et les données ROM de l'APP_CPU tenues à part ; hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo, real_time_stats, blink avec son ruban de LED et adc oneshot_read d'ESP-IDF v5.4 compilés sans modification pour la cible esp32 sur l'ESP32-DevKitC V4, de la bannière de la ROM jusqu'à esp_restart ; l'image ROM est identique octet pour octet à l'esp32-v3-rom.bin du QEMU d'Espressif, et la vue qu'en a l'APP_CPU à son esp32-v3-rom-app.bin.", "omissions": [ "le Wi-Fi, le Bluetooth et l'étage RF (WIFI_MAC, BB, NRX, FE : leurs registres gardent ce qui y est écrit)", "les instructions de l'accélérateur double précision (F64ADDC, F64ITER et les autres : leurs encodages sont publics, pas ce qu'elles font ; un programme qui en exécute une s'arrête avec un diagnostic), bien que ses registres soient conservés", "la PSRAM (DRAM1), le remappage de démarrage, les permissions d'accès APB/AHB, les régions verrouillées et le timing des caches (les pages mappées sont lues entières), les interruptions d'erreur d'accès de la MMU", "RMT : la mise hors tension de la mémoire (MEM_PD), le champ STATE de STATUS et les bits d'erreur côté APB, la porteuse sur une sortie inactive", + "les ADC SAR : la non-linéarité au-delà d'environ 2450 mV à 11 dB et l'écart d'une puce à la droite ajustée, le bruit, les conversions de l'ULP et du contrôleur numérique (DMA, I2S), le capteur à effet Hall, l'arbitrage de l'ADC2 avec le Wi-Fi, le routage d'un plot vers l'ADC par l'IO MUX RTC (non vérifié)", "I2C : le mode hors FIFO (NONFIFO_EN, RX_REC_FULL, TX_SEND_EMPTY), SAMPLE_SCL_LEVEL, les filtres d'entrée (seulement leur part du temps haut de SCL), le contrôle R/W de l'adressage sur 10 bits", - "les périphériques sans modèle pour l'instant : I2S0/1, SPI2/3 (HSPI, VSPI), MCPWM, PCNT, TWAI, SDMMC, EMAC, les ADC SAR, les DAC, le capteur tactile, l'IO MUX RTC et le coprocesseur ULP, les accélérateurs AES et RSA, le chiffrement de la flash", + "les périphériques sans modèle pour l'instant : I2S0/1, SPI2/3 (HSPI, VSPI), MCPWM, PCNT, TWAI, SDMMC, EMAC, les DAC, le capteur tactile, l'IO MUX RTC et le coprocesseur ULP, les accélérateurs AES et RSA, le chiffrement de la flash", "les modes de veille (veille légère et profonde, les domaines d'alimentation), la détection de baisse de tension", "le contrôle du schéma de codage 3/4 de l'eFuse, la protection en écriture par champ du BLOCK0" ] diff --git a/core/mcu/esp32/soc/adc.c b/core/mcu/esp32/soc/adc.c new file mode 100644 index 000000000..2400152b0 --- /dev/null +++ b/core/mcu/esp32/soc/adc.c @@ -0,0 +1,204 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* adc.c -- the ESP32's SAR ADCs as the RTC controller drives them (SENS, + * TRM v5 chapter 29): one conversion at a time on SAR ADC1 or ADC2, as + * ESP-IDF's adc_oneshot and Arduino's analogRead use them. + * + * - ADC1 reads GPIO36, 37, 38, 39, 32, 33, 34, 35 (channels 0-7), ADC2 + * GPIO4, 0, 2, 15, 13, 12, 14, 27, 25, 26 (channels 0-9). + * - MEAS1_START_SAR (SAR_MEAS_START1) rising starts a conversion of the + * channel SAR1_EN_PAD selects (its lowest bit set), at SAR_ATTEN1's + * attenuation for it (two bits a channel: 0, 2.5, 6, 11 dB); MEAS1_DONE_SAR + * reads 0 until (SAR1_SAMPLE_CYCLE + bits + 1) SAR clocks have passed + * (RTC_FAST_CLK's 8 MHz divided by SAR1_CLK_DIV), then 1, with the code + * in MEAS1_DATA_SAR; a new start clears it. ADC2 the same with + * SAR_MEAS_START2, SAR_ATTEN2 and SAR_READ_CTRL2. + * - The code is SAR1_SAMPLE_BIT's 9 to 12 bits wide (the 12-bit code + * shifted down). The converter's own output is inverted; SAR1_DATA_INV + * (SAR2_DATA_INV) set, as ESP-IDF sets it, gives the code itself. + * - The transfer: the line ESP-IDF's Vref calibration fits for the chip + * (esp_adc's adc_cali_line_fitting.c, from Espressif's + * characterisation of the ESP32): V = a code / 65536 + b mV, with a = + * Vref x scale / 4096, scale and b per unit and attenuation (ADC1: + * 57431, 76236, 105481, 196602 and 75, 78, 107, 142 mV; ADC2: 57236, + * 76175, 105678, 197170 and 63, 66, 89, 128 mV), Vref the chip's eFuse + * ADC_VREF (1100 mV + 7 mV a step); a voltage below b reads 0, one past + * the range 4095. ESP-IDF's calibrated voltage is so the pin's within + * its rounding. + * Where it differs inside: the RTC IO MUX's routing of a pad to the ADC + * (RTC_IO's ADC pad and FUN_SEL bits) is not checked: the pad's voltage + * is read whatever its function. Not yet: the nonlinearity above about + * 2450 mV at 11 dB (ESP-IDF's LUT region) and a chip's spread from the + * fitted line, noise, the ULP's and the digital controller's (DMA, + * I2S) conversions, the Hall sensor, ADC2's arbitration with Wi-Fi, the + * amplifier (SAR_MEAS_WAIT, the AMP FSM). */ +#include + +#include "mcu/esp32/soc/esp32.h" + +#define READ_CTRL 0x00u +#define ATTEN1 0x34u +#define ATTEN2 0x38u +#define MEAS_START1 0x54u +#define READ_CTRL2 0x90u +#define MEAS_START2 0x94u +#define START_SAR (1u << 17) +#define DONE_SAR (1u << 16) +#define RTC_FAST_HZ 8000000.0 + +struct e32_adc { + struct esk_mach *s; + struct esk_rf_periph *p; + int busy[2]; + u64 due[2]; /* the conversion's end, in core cycles */ + u32 code[2]; + int clocked; +}; + +static const int pads[2][10] = { + { 36, 37, 38, 39, 32, 33, 34, 35, -1, -1 }, + { 4, 0, 2, 15, 13, 12, 14, 27, 25, 26 }, +}; +static const double scale[2][4] = { { 57431, 76236, 105481, 196602 }, { 57236, 76175, 105678, 197170 } }; +static const double offset_mv[2][4] = { { 75, 78, 107, 142 }, { 63, 66, 89, 128 } }; + +static u32 rd(const struct e32_adc *k, u32 off) { return esk_rf_read(k->p, off, 4); } + +static void hw(struct e32_adc *k, u32 off, u32 v) +{ + struct esk_rf_reg *r = esk_rf_reg_at(k->p, off); + if (r) + esk_rf_hw(r, v); +} + +/* the chip's ADC Vref in mV, from the eFuse (BLOCK0 word 4's ADC_VREF: + * sign and magnitude, 7 mV a step) */ +static double vref_mv(struct esk_mach *m) +{ + u32 bits = (esk_esp32_efuse_word(m, 0, 4) >> 8) & 0x1fu; + int steps = (int)(bits & 0xfu); + return 1100.0 + (double)((bits & 0x10u) ? -steps : steps) * 7.0; +} + +static u32 convert(struct e32_adc *k, int unit) +{ + struct esk_mach *s = k->s; + u32 start = rd(k, unit ? MEAS_START2 : MEAS_START1), ctrl = rd(k, unit ? READ_CTRL2 : READ_CTRL); + u32 pads_on = (start >> 19) & 0xfffu, bits = 9u + ((ctrl >> 16) & 3u), atten; + int ch, gpio; + double v = 0, a, d; + u32 code; + for (ch = 0; ch < 10 && !(pads_on & (1u << ch)); ch++) + ; + gpio = ch < 10 ? pads[unit][ch] : -1; + atten = ch < 10 ? (rd(k, unit ? ATTEN2 : ATTEN1) >> (2 * ch)) & 3u : 3u; + if (gpio >= 0 && s->host.pin_volts && !s->host.pin_volts(s->host.ctx, gpio / 16, gpio % 16, &v)) + v = 0; + a = vref_mv(s) * scale[unit][atten] / 4096.0; + d = (v * 1000.0 - offset_mv[unit][atten]) * 65536.0 / a + 0.5; + code = d <= 0 ? 0u : d >= 4095.0 ? 4095u : (u32)d; + code >>= 12u - bits; + if (!(ctrl & (1u << (unit ? 29 : 28)))) /* the converter's own, inverted */ + code = ((1u << bits) - 1u) - code; + return code; +} + +static void clocking(struct e32_adc *k) +{ + esk_rf_clock(k->p, &k->clocked, k->busy[0] || k->busy[1]); +} + +static void finish(struct e32_adc *k, int unit) +{ + u32 off = unit ? MEAS_START2 : MEAS_START1; + k->busy[unit] = 0; + hw(k, off, (rd(k, off) & ~0xffffu) | DONE_SAR | (k->code[unit] & 0xffffu)); +} + +static void start(struct e32_adc *k, int unit) +{ + u32 off = unit ? MEAS_START2 : MEAS_START1, ctrl = rd(k, unit ? READ_CTRL2 : READ_CTRL); + u32 div = ctrl & 0xffu, cycles = ((ctrl >> 8) & 0xffu) + 9u + ((ctrl >> 16) & 3u) + 1u; + double t = (double)cycles * (double)(div ? div : 1u) / RTC_FAST_HZ; + hw(k, off, rd(k, off) & ~DONE_SAR); + k->code[unit] = convert(k, unit); + k->busy[unit] = 1; + k->due[unit] = k->s->cycles + (u64)(t * (double)esk_mach_core_hz(k->s) + 0.5) + 1u; +} + +static void adc_tick(void *ctx, u32 cycles) +{ + struct e32_adc *k = (struct e32_adc *)ctx; + int u; + (void)cycles; + for (u = 0; u < 2; u++) + if (k->busy[u] && k->s->cycles >= k->due[u]) + finish(k, u); + clocking(k); +} + +static int adc_next(void *ctx, u32 *cycles) +{ + struct e32_adc *k = (struct e32_adc *)ctx; + u64 best = 0; + int u, any = 0; + for (u = 0; u < 2; u++) { + u64 d; + if (!k->busy[u]) + continue; + d = k->due[u] > k->s->cycles ? k->due[u] - k->s->cycles : 1u; + if (!any || d < best) + best = d; + any = 1; + } + if (!any) + return 0; + *cycles = best > 0xffffffffu ? 0xffffffffu : (u32)best; + return 1; +} + +static u32 adc_read(void *ctx, u32 off, int n) +{ + struct e32_adc *k = (struct e32_adc *)ctx; + return esk_rf_read(k->p, off, n); +} + +static void adc_write(void *ctx, u32 off, int n, u32 v) +{ + struct e32_adc *k = (struct e32_adc *)ctx; + u32 w = off & ~3u; + int unit = w == MEAS_START2 ? 1 : 0; + u32 was = (w == MEAS_START1 || w == MEAS_START2) ? rd(k, w) : 0; + esk_rf_write(k->p, off, n, v); + if (w == MEAS_START1 || w == MEAS_START2) { + u32 now = rd(k, w); + /* DATA and DONE are the converter's: what was written there is not kept */ + hw(k, w, (now & ~(0xffffu | DONE_SAR)) | (was & (0xffffu | DONE_SAR))); + if ((now & START_SAR) && !(was & START_SAR)) + start(k, unit); + } + clocking(k); +} + +static void adc_reset(void *ctx) +{ + struct e32_adc *k = (struct e32_adc *)ctx; + k->busy[0] = k->busy[1] = 0; + k->clocked = 0; + clocking(k); +} + +const struct esk_dev_ops esk_esp32_adc_ops = { + "esp32-sens", adc_read, adc_write, adc_reset, adc_tick, adc_next, +}; + +void *esk_esp32_adc_create(struct esk_mach *s, struct esk_rf_periph *p) +{ + struct e32_adc *k = (struct e32_adc *)esk_mach_alloc(s, sizeof *k); + if (!k) + return 0; + memset(k, 0, sizeof *k); + k->s = s; + k->p = p; + return k; +} diff --git a/core/mcu/esp32/soc/esp32.h b/core/mcu/esp32/soc/esp32.h index a6df40ba5..3604387de 100644 --- a/core/mcu/esp32/soc/esp32.h +++ b/core/mcu/esp32/soc/esp32.h @@ -19,6 +19,7 @@ * ledc.c the LED PWM controller * i2c.c I2C0 and I2C1 * rmt.c the remote control peripheral's eight channels + * adc.c the SAR ADCs as SENS's RTC controller drives them * misc.c the random number generator, the AHB view of the * peripherals */ #ifndef ESK_MCU_ESP32_SOC_H @@ -58,6 +59,8 @@ extern const struct esk_dev_ops esk_esp32_i2c_ops; void *esk_esp32_i2c_create(struct esk_mach *s, struct esk_rf_periph *p); extern const struct esk_dev_ops esk_esp32_rmt_ops; void *esk_esp32_rmt_create(struct esk_mach *s, struct esk_rf_periph *p); +extern const struct esk_dev_ops esk_esp32_adc_ops; +void *esk_esp32_adc_create(struct esk_mach *s, struct esk_rf_periph *p); extern const struct esk_dev_ops esk_esp32_sha_ops; void *esk_esp32_sha_create(struct esk_mach *s, struct esk_rf_periph *p); /* the RNG and the AHB view of the peripherals (misc.c): 0, or -1 */ diff --git a/core/mcu/models.c b/core/mcu/models.c index 0f603f9be..a1fbe3149 100644 --- a/core/mcu/models.c +++ b/core/mcu/models.c @@ -74,6 +74,7 @@ static const struct esk_rf_model_type model_types[] = { { "esp32-ledc", &esk_esp32_ledc_ops, esk_esp32_ledc_create }, { "esp32-i2c", &esk_esp32_i2c_ops, esk_esp32_i2c_create }, { "esp32-rmt", &esk_esp32_rmt_ops, esk_esp32_rmt_create }, + { "esp32-sens", &esk_esp32_adc_ops, esk_esp32_adc_create }, { "esp32-iomux", &esk_esp32_iomux_ops, esk_esp32_iomux_create }, { "esp32c3-intmtx", &esk_esp32c3_intmtx_ops, esk_esp32c3_intmtx_create }, { "esp32c3-uart", &esk_esp32c3_uart_ops, esk_esp32c3_uart_create }, diff --git a/docs/STATUS.md b/docs/STATUS.md index 41b396e1c..8d3bbb164 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -17,10 +17,11 @@ and through `esp_restart` (rst:0xc SW_CPU_RESET). Evidence: `test_lx6` scenarios `esp32_hello_world` (9/9), `esp32_blink` (6/6), `esp32_ledc_basic` (2/2), `esp32_i2c_eeprom` (3/3, I2C0 writing and reading a 24LC256 at 400 kHz), `esp32_gptimer` (17/17), `esp32_uart_echo` -(6/6), `esp32_real_time_stats` (10/10) and `esp32_blink_strip` (7/7, a -WS2812B through the RMT). Not yet: Wi-Fi and Bluetooth, the +(6/6), `esp32_real_time_stats` (10/10) , `esp32_blink_strip` (7/7, a +WS2812B through the RMT) and `esp32_adc_oneshot_read` (6/6, ADC1 and ADC2 +with the eFuse Vref's calibration). Not yet: Wi-Fi and Bluetooth, the double-precision accelerator's instructions, the peripherals the -description's fidelity lists (I2S, SPI2/3, ADC, ...), +description's fidelity lists (I2S, SPI2/3, the DACs, ...), sleep. The standalone harness below remains as its own qualification fixtures. diff --git a/docs/coverage.md b/docs/coverage.md index 8245e8da3..dd3902425 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -8,7 +8,7 @@ evidence for each model is in [STATUS.md](STATUS.md). | MCU | Modelled | Registers only | Missing | Peripherals | |---|---|---|---|---| -| ESP32 | 18 | 0 | 27 | 45 | +| ESP32 | 19 | 0 | 26 | 45 | | ESP32-C3 | 35 | 0 | 0 | 35 | | ATmega328P | 15 | 0 | 0 | 15 | | PIC24FJ1024GB610 | 11 | 4 | 17 | 32 | @@ -23,7 +23,7 @@ evidence for each model is in [STATUS.md](STATUS.md). ## ESP32 (`espressif/esp32`) Boards: ESP32-DevKitC V4. -18 of 45 peripherals modelled, 0 registers only, 27 missing. +19 of 45 peripherals modelled, 0 registers only, 26 missing. | Peripheral | Model | State | |---|---|---| @@ -40,7 +40,7 @@ Boards: ESP32-DevKitC V4. | FRC_TIMER | — | **missing** | | RTC_CNTL | `esp32-rtc` | modelled | | RTC_IO | — | **missing** | -| SENS | — | **missing** | +| SENS | `esp32-sens` | modelled | | RTC_I2C | — | **missing** | | IO_MUX | `esp32-iomux` | modelled | | HINF | — | **missing** | @@ -73,7 +73,7 @@ Boards: ESP32-DevKitC V4. | I2S1 | — | **missing** | | UART2 | `esp32-uart` | modelled | -Declared omissions: Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written); the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept; PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts; RMT: the memory's power-down (MEM_PD), STATUS's STATE and the APB-side error bits, the carrier on an idle output; I2C: the non-FIFO mode (NONFIFO_EN, RX_REC_FULL, TX_SEND_EMPTY), SAMPLE_SCL_LEVEL, the input filters (only their share of the SCL high time), 10-bit addressing's R/W check; the peripherals without a model yet: I2S0/1, SPI2/3 (HSPI, VSPI), MCPWM, PCNT, TWAI, SDMMC, EMAC, the SAR ADCs, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption; sleep modes (light and deep sleep, the power domains), brown-out; the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection. +Declared omissions: Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written); the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept; PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts; RMT: the memory's power-down (MEM_PD), STATUS's STATE and the APB-side error bits, the carrier on an idle output; the SAR ADCs: the nonlinearity above about 2450 mV at 11 dB and a chip's spread from the fitted line, noise, the ULP's and the digital controller's (DMA, I2S) conversions, the Hall sensor, ADC2's arbitration with Wi-Fi, the RTC IO MUX's routing of a pad to the ADC (not checked); I2C: the non-FIFO mode (NONFIFO_EN, RX_REC_FULL, TX_SEND_EMPTY), SAMPLE_SCL_LEVEL, the input filters (only their share of the SCL high time), 10-bit addressing's R/W check; the peripherals without a model yet: I2S0/1, SPI2/3 (HSPI, VSPI), MCPWM, PCNT, TWAI, SDMMC, EMAC, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption; sleep modes (light and deep sleep, the power domains), brown-out; the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection. ## ESP32-C3 (`espressif/esp32c3`) diff --git a/tests/vendor/esp-idf/examples/esp32_adc_oneshot_read/project.json b/tests/vendor/esp-idf/examples/esp32_adc_oneshot_read/project.json new file mode 100644 index 000000000..7262f65b1 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_adc_oneshot_read/project.json @@ -0,0 +1,112 @@ +{ + "schema_version": 1, + "kind": "project", + "name": "esp-idf-esp32_adc_oneshot_read", + "description": "ESP-IDF's adc/oneshot_read on the ESP32-DevKitC V4: 1.1 V on IO32 (ADC1 channel 4), 2.2 V on IO33 (ADC1 channel 5) and 0.5 V on IO4 (ADC2 channel 0) from three sources.", + "policy": "electrical", + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc", + "layout": { + "x": 360, + "y": 120 + }, + "firmware": { + "image": "flash.bin", + "format": "bin" + } + } + ], + "components": [ + { + "id": "USB", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5 + }, + "layout": { + "x": 120, + "y": 160 + } + }, + { + "id": "GND1", + "component": "emblink/gnd", + "layout": { + "x": 120, + "y": 320 + } + }, + { + "id": "V2", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 1.1 + }, + "layout": { + "x": 120, + "y": 440 + } + }, + { + "id": "V3", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 2.2 + }, + "layout": { + "x": 120, + "y": 560 + } + }, + { + "id": "V4", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 0.5 + }, + "layout": { + "x": 120, + "y": 700 + } + } + ], + "nets": { + "USB5V": [ + "USB.+", + "B1.VBUS" + ], + "GND": [ + "USB.-", + "GND1.GND", + "B1.GND1", + "V2.-", + "V3.-", + "V4.-" + ], + "IO32": [ + "V2.+", + "B1.IO32" + ], + "IO33": [ + "V3.+", + "B1.IO33" + ], + "IO4": [ + "V4.+", + "B1.IO4" + ] + }, + "probes": [ + { + "id": "console", + "target": "serial:B1", + "kind": "serial" + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/examples/esp32_adc_oneshot_read/scenario.json b/tests/vendor/esp-idf/examples/esp32_adc_oneshot_read/scenario.json new file mode 100644 index 000000000..9d0d3b8b0 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_adc_oneshot_read/scenario.json @@ -0,0 +1,58 @@ +{ + "schema_version": 1, + "kind": "scenario", + "name": "esp32_adc_oneshot_read", + "until": "3500ms", + "description": "adc/oneshot_read on the ESP32: ADC1's channels 4 and 5 and ADC2's channel 0 at 12 dB, one read a second each, with the eFuse's Vref calibration (ESP-IDF's line fitting, Vref 1170 mV on this chip). The model's transfer is that fitted line, so the raw codes are its inverse and the calibrated voltages the sources' within a millivolt. ADC1 at 12 dB: a = 1170 x 196602 / 4096 = 56158, b = 142 mV, so 1.1 V is (1100 - 142) x 65536 / 56158 = 1118 and 2.2 V 2402; ADC2: a = 56320, b = 128 mV, so 0.5 V is 433.", + "inputs": [], + "assertions": [ + { + "id": "adc1-ch4-raw", + "at": "3400ms", + "probe": "console", + "expect": { + "contains": "ADC1 Channel[4] Raw Data: 1118\r\n" + } + }, + { + "id": "adc1-ch4-mv", + "at": "3400ms", + "probe": "console", + "expect": { + "contains": "ADC1 Channel[4] Cali Voltage: 1100 mV\r\n" + } + }, + { + "id": "adc1-ch5-raw", + "at": "3400ms", + "probe": "console", + "expect": { + "contains": "ADC1 Channel[5] Raw Data: 2402\r\n" + } + }, + { + "id": "adc1-ch5-mv", + "at": "3400ms", + "probe": "console", + "expect": { + "contains": "ADC1 Channel[5] Cali Voltage: 2200 mV\r\n" + } + }, + { + "id": "adc2-ch0-raw", + "at": "3400ms", + "probe": "console", + "expect": { + "contains": "ADC2 Channel[0] Raw Data: 433\r\n" + } + }, + { + "id": "adc2-ch0-mv", + "at": "3400ms", + "probe": "console", + "expect": { + "contains": "ADC2 Channel[0] Cali Voltage: 500 mV\r\n" + } + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/run.py b/tests/vendor/esp-idf/run.py index 4c8d9c818..534bea08a 100644 --- a/tests/vendor/esp-idf/run.py +++ b/tests/vendor/esp-idf/run.py @@ -79,6 +79,7 @@ "esp32_uart_echo": "examples/peripherals/uart/uart_echo", "esp32_real_time_stats": "examples/system/freertos/real_time_stats", "esp32_blink_strip": "examples/get-started/blink", + "esp32_adc_oneshot_read": "examples/peripherals/adc/oneshot_read", } # the target an example is built for, when not the ESP32-C3 (its board in # examples//project.json), and the toolchain that target needs @@ -91,6 +92,7 @@ "esp32_uart_echo": "esp32", "esp32_real_time_stats": "esp32", "esp32_blink_strip": "esp32", + "esp32_adc_oneshot_read": "esp32", } TOOLCHAINS = {"esp32c3": "riscv32-esp-elf-gcc", "esp32": "xtensa-esp32-elf-gcc"} # examples built only for another's project (with examples//'s From 3d310bf43a24578f8f4f0f771ef11d5a9d544e03 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Sun, 8 Nov 2026 05:36:48 +0000 Subject: [PATCH 54/57] ESP32: Arduino-ESP32's sketches on the DevKitC; DBREAK's mask Five sketches compiled for "ESP32 Dev Module" with Arduino-ESP32 3.2.0 run unmodified (tests/vendor/arduino-esp32, ESP32_*): AnalogReadSerial and ReadAnalogVoltage on VP, DigitalReadSerial, GetChipID (ESP32-D0WD-V3 Rev 301, two cores) and RepeatTimer stopped by the board's Boot button. Arduino turns on FreeRTOS's end-of-stack watchpoint, and its loopTask stopped at once with "Stack canary watchpoint triggered": the core took DBREAKC's MASK as the address bits ignored, where its set bits are the low bits compared (ESP-IDF's 32-byte watchpoint writes 0x20). test_lx6 gains a store watchpoint over 32 bytes, missed just past it and taken inside it. --- CHANGELOG.md | 6 +- catalog/mcus/espressif/esp32/mcu.json | 9 +- core/mcu/xtensa/lx6.c | 4 +- docs/STATUS.md | 4 +- tests/unit/gen_lx6_programs.py | 27 +++++ tests/unit/lx6_programs.h | 109 ++++++++++++++++++ tests/unit/test_lx6.c | 13 +++ .../ESP32_AnalogReadSerial/project.json | 78 +++++++++++++ .../ESP32_AnalogReadSerial/scenario.json | 18 +++ .../ESP32_DigitalReadSerial/project.json | 90 +++++++++++++++ .../ESP32_DigitalReadSerial/scenario.json | 47 ++++++++ .../examples/ESP32_GetChipID/project.json | 61 ++++++++++ .../examples/ESP32_GetChipID/scenario.json | 34 ++++++ .../ESP32_ReadAnalogVoltage/project.json | 78 +++++++++++++ .../ESP32_ReadAnalogVoltage/scenario.json | 18 +++ .../examples/ESP32_RepeatTimer/project.json | 61 ++++++++++ .../examples/ESP32_RepeatTimer/scenario.json | 47 ++++++++ tests/vendor/arduino-esp32/run.py | 27 ++++- 18 files changed, 718 insertions(+), 13 deletions(-) create mode 100644 tests/vendor/arduino-esp32/examples/ESP32_AnalogReadSerial/project.json create mode 100644 tests/vendor/arduino-esp32/examples/ESP32_AnalogReadSerial/scenario.json create mode 100644 tests/vendor/arduino-esp32/examples/ESP32_DigitalReadSerial/project.json create mode 100644 tests/vendor/arduino-esp32/examples/ESP32_DigitalReadSerial/scenario.json create mode 100644 tests/vendor/arduino-esp32/examples/ESP32_GetChipID/project.json create mode 100644 tests/vendor/arduino-esp32/examples/ESP32_GetChipID/scenario.json create mode 100644 tests/vendor/arduino-esp32/examples/ESP32_ReadAnalogVoltage/project.json create mode 100644 tests/vendor/arduino-esp32/examples/ESP32_ReadAnalogVoltage/scenario.json create mode 100644 tests/vendor/arduino-esp32/examples/ESP32_RepeatTimer/project.json create mode 100644 tests/vendor/arduino-esp32/examples/ESP32_RepeatTimer/scenario.json diff --git a/CHANGELOG.md b/CHANGELOG.md index feef4881f..ea0176189 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,11 @@ versions follow semantic versioning. There is no release yet. 24LC256, `esp32_gptimer` 17/17, `esp32_uart_echo` 6/6 on UART2, `esp32_real_time_stats` 10/10 across both cores, `esp32_blink_strip` 7/7 on a WS2812B through the RMT, - `esp32_adc_oneshot_read` 6/6 with the eFuse Vref's calibration; the UARTs keep + `esp32_adc_oneshot_read` 6/6 with the eFuse Vref's calibration; + Arduino-ESP32's AnalogReadSerial, ReadAnalogVoltage, DigitalReadSerial, + GetChipID and RepeatTimer for "ESP32 Dev Module" in + `tests/vendor/arduino-esp32` (`ESP32_*`); DBREAKC's MASK now marks the + address bits compared, as ESP-IDF's stack-end watchpoint has it; the UARTs keep MEM_RX_STATUS's addresses, from which ESP-IDF counts the bytes waiting); `test_lx6` checks the core with programs from Espressif's assembler. ### Original ESP32 digital per-pin hold diff --git a/catalog/mcus/espressif/esp32/mcu.json b/catalog/mcus/espressif/esp32/mcu.json index b97f0ba30..a2f031e7f 100644 --- a/catalog/mcus/espressif/esp32/mcu.json +++ b/catalog/mcus/espressif/esp32/mcu.json @@ -275,7 +275,7 @@ }, "fidelity": { "functional": { - "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts, MEM_RX_STATUS's addresses that ESP-IDF counts the bytes waiting from), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), LEDC (eight high-speed and eight low-speed PWM channels on eight timers, fading, on the matrix's ledc_hs/ls_sig_out), I2C0 and I2C1 (master and slave on the open-drain lines through the matrix, the 16 commands with RSTART, WRITE, READ, STOP and END, the bus timing in APB_CLK cycles, ACK checks, arbitration, the SCL timeout, the 32-byte FIFOs through DATA and its AHB view, 7- and 10-bit slave addresses; ESP-IDF's i2c_eeprom runs on a 24LC256), the RMT (eight channels sending or receiving through the matrix, 64-word blocks of the 512-word RAM, end markers, continuous mode, wrap, TX_LIM's threshold, the carrier, the receiver's idle threshold and filter, the RAM through CHnDATA; ESP-IDF's led_strip lights a WS2812B), the SAR ADCs as SENS's RTC controller drives them (one conversion at a time on ADC1's eight and ADC2's ten pads, the attenuations, 9 to 12 bits, the converter's inverted output and DATA_INV, the conversion's time; the transfer the line ESP-IDF's Vref calibration fits for the chip, its eFuse Vref), PERIP_RST_EN's resets of I2C0, I2C1 and LEDC, the internal analog I2C master, the random number generator and the peripherals' AHB view. The evidence: test_lx6.c's programs assembled by Espressif's assembler (integer, shifts, multiply and divide, loops, branches, memory with S32C1I, windowed recursion through the ROM's own window vectors, SYSCALL, the CCOMPARE timer interrupt, FPU, MAC16) and the APP_CPU's ROM data kept apart; ESP-IDF v5.4's hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo, real_time_stats, blink with its LED strip and adc oneshot_read built unmodified for the esp32 target on the ESP32-DevKitC V4, from the ROM's banner to esp_restart; the ROM image equals Espressif QEMU's esp32-v3-rom.bin byte for byte, and the APP_CPU's view of it QEMU's esp32-v3-rom-app.bin.", + "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts, MEM_RX_STATUS's addresses that ESP-IDF counts the bytes waiting from), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), LEDC (eight high-speed and eight low-speed PWM channels on eight timers, fading, on the matrix's ledc_hs/ls_sig_out), I2C0 and I2C1 (master and slave on the open-drain lines through the matrix, the 16 commands with RSTART, WRITE, READ, STOP and END, the bus timing in APB_CLK cycles, ACK checks, arbitration, the SCL timeout, the 32-byte FIFOs through DATA and its AHB view, 7- and 10-bit slave addresses; ESP-IDF's i2c_eeprom runs on a 24LC256), the RMT (eight channels sending or receiving through the matrix, 64-word blocks of the 512-word RAM, end markers, continuous mode, wrap, TX_LIM's threshold, the carrier, the receiver's idle threshold and filter, the RAM through CHnDATA; ESP-IDF's led_strip lights a WS2812B), the SAR ADCs as SENS's RTC controller drives them (one conversion at a time on ADC1's eight and ADC2's ten pads, the attenuations, 9 to 12 bits, the converter's inverted output and DATA_INV, the conversion's time; the transfer the line ESP-IDF's Vref calibration fits for the chip, its eFuse Vref), PERIP_RST_EN's resets of I2C0, I2C1 and LEDC, the internal analog I2C master, the random number generator and the peripherals' AHB view. The evidence: test_lx6.c's programs assembled by Espressif's assembler (integer, shifts, multiply and divide, loops, branches, memory with S32C1I, windowed recursion through the ROM's own window vectors, SYSCALL, the CCOMPARE timer interrupt, FPU, MAC16) and the APP_CPU's ROM data kept apart; ESP-IDF v5.4's hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo, real_time_stats, blink with its LED strip and adc oneshot_read built unmodified for the esp32 target on the ESP32-DevKitC V4, from the ROM's banner to esp_restart; Arduino-ESP32 3.2.0's sketches for \"ESP32 Dev Module\" (AnalogReadSerial, ReadAnalogVoltage, DigitalReadSerial, GetChipID, RepeatTimer, with FreeRTOS's stack-end watchpoints on); the ROM image equals Espressif QEMU's esp32-v3-rom.bin byte for byte, and the APP_CPU's view of it QEMU's esp32-v3-rom-app.bin.", "omissions": [ "Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written)", "the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept", @@ -298,7 +298,8 @@ "repo:tests/vendor/esp-idf/examples/esp32_uart_echo/scenario.json", "repo:tests/vendor/esp-idf/examples/esp32_real_time_stats/scenario.json", "repo:tests/vendor/esp-idf/examples/esp32_blink_strip/scenario.json", - "repo:tests/vendor/esp-idf/examples/esp32_adc_oneshot_read/scenario.json" + "repo:tests/vendor/esp-idf/examples/esp32_adc_oneshot_read/scenario.json", + "repo:tests/vendor/arduino-esp32/run.py" ] }, "timed": { @@ -404,11 +405,11 @@ }, "i18n": { "fr": { - "source": "bff3ddeda639", + "source": "a076e310391c", "description": "Espressif ESP32 (ESP32-D0WD-V3, révision de puce v3.1) : deux cœurs Xtensa LX6 jusqu'à 240 MHz, 520 Kio de SRAM, 448 Kio de ROM, 16 Kio de mémoire RTC, une flash SPI externe derrière les caches des cœurs, Wi-Fi et Bluetooth ; le MCU de l'ESP32-DevKitC. La ROM d'Espressif, le bootloader de second niveau d'ESP-IDF et les applications ESP-IDF tournent sur les deux cœurs ; les radios ne sont pas encore modélisées.", "fidelity": { "functional": { - "claims": "Les deux cœurs Xtensa LX6 (mcu/xtensa/lx6.c, lx6fp.c) tels que les configure le core-isa.h d'Espressif : les instructions de base et de densité, les registres fenêtrés (64, le contrôle de débordement de chaque instruction, CALL4/8/12, ENTRY, RETW et son sous-débordement, MOVSP, ROTW, L32E/S32E), les boucles sans surcoût, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, les booléens, S32C1I avec SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), le paquet MAC16, la FPU simple précision (modes d'arrondi et drapeaux IEEE ; les séquences de division et de racine carrée qu'exécute libgcc donnent des résultats correctement arrondis), les exceptions XEA2 avec vecteurs relocalisables, 32 interruptions aux niveaux et types de core-isa.h (niveau, front, logicielles, les temporisateurs CCOMPARE0-2 contre CCOUNT, NMI), RSIL, WAITI, RFI, BREAK, IBREAK, DBREAK et ICOUNT de l'option de débogage, la protection par régions (huit régions de 512 Mo, 15 sans accès), les chargements et écritures non alignés, et les accès de données en 32 bits seulement du bus d'instructions. Les deux cœurs partagent la machine, l'APP_CPU maintenu en reset jusqu'à ce que DPORT le libère et voyant la ROM avec ses pointeurs vers les données ROM du PRO_CPU déplacés vers les siennes, comme dans l'image ROM APP_CPU d'Espressif. Le démarrage : la ROM d'Espressif (esp32_rev300_rom.elf, révision v3) depuis le vecteur de reset, sa bannière, les broches de configuration (GPIO_STRAP : SPI_FAST_FLASH_BOOT, ou le mode téléchargement avec GPIO0 bas), les causes de reset (RTC_CNTL), le contrôle anti-glitch de l'eFuse, la lecture de la flash par SPI1, le bootloader de second niveau d'ESP-IDF et sa vérification d'image sur l'accélérateur SHA, la MMU flash des caches, et l'application sur les deux cœurs. Les modèles : DPORT (reset, horloge et blocage de l'APP_CPU, CPU_PER_CONF, vidage et état des caches, tables de la MMU flash, les 69 sources de la matrice d'interruptions, les interruptions inter-CPU), RTC_CNTL (causes de reset, le temporisateur RTC, le chien de garde RTC avec sa protection au démarrage sur flash, les resets et blocages logiciels, les registres de stockage, SOC_CLK_SEL), APB_CTRL (les horloges CPU et APB depuis XTAL, PLL ou RC_FAST), le contrôleur eFuse avec les bits d'usine de la puce (ESP32-D0WD-V3 v3.1, une MAC avec son CRC, ou celle des réglages de la carte), GPIO et IO_MUX (40 plots, la matrice, tirages, drain ouvert, interruptions sur chaque CPU, le verrou de configuration), UART0-2 (FIFO, trames au débit, interruptions, les adresses de MEM_RX_STATUS d'après lesquelles ESP-IDF compte les octets en attente), TIMG0/1 (T0, T1, le temporisateur LAC, le MWDT avec sa protection au démarrage sur flash, la calibration de RTC_SLOW_CLK), SPI0/SPI1 avec la flash Giantec GT25Q32A du module (C4 60 16, l'identifiant que rapporte la carte du propriétaire), l'accélérateur SHA (SHA-1, -256, -384, -512), LEDC (huit canaux MLI rapides et huit lents sur huit temporisateurs, le fondu, sur ledc_hs/ls_sig_out de la matrice), I2C0 et I2C1 (maître et esclave sur les lignes à drain ouvert à travers la matrice, les 16 commandes avec RSTART, WRITE, READ, STOP et END, le timing du bus en cycles d'APB_CLK, les contrôles d'ACK, l'arbitrage, le délai d'attente sur SCL, les FIFO de 32 octets par DATA et sa vue AHB, les adresses esclaves sur 7 et 10 bits ; l'exemple i2c_eeprom d'ESP-IDF fonctionne avec une 24LC256), le RMT (huit canaux qui émettent ou reçoivent à travers la matrice, des blocs de 64 mots de la RAM de 512 mots, les marqueurs de fin, le mode continu, le rebouclage, le seuil de TX_LIM, la porteuse, le seuil d'inactivité et le filtre du récepteur, la RAM par CHnDATA ; le led_strip d'ESP-IDF allume une WS2812B), les ADC SAR tels que les pilote le contrôleur RTC de SENS (une conversion à la fois sur les huit plots de l'ADC1 et les dix de l'ADC2, les atténuations, 9 à 12 bits, la sortie inversée du convertisseur et DATA_INV, la durée de la conversion ; la caractéristique est la droite que la calibration Vref d'ESP-IDF ajuste pour la puce, avec son Vref d'eFuse), les resets de PERIP_RST_EN pour I2C0, I2C1 et LEDC, le maître I2C analogique interne, le générateur de nombres aléatoires et la vue AHB des périphériques. Les preuves : les programmes de test_lx6.c assemblés par l'assembleur d'Espressif (entiers, décalages, multiplication et division, boucles, branchements, mémoire avec S32C1I, récursion fenêtrée à travers les vecteurs de fenêtre de la ROM, SYSCALL, l'interruption du temporisateur CCOMPARE, FPU, MAC16) et les données ROM de l'APP_CPU tenues à part ; hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo, real_time_stats, blink avec son ruban de LED et adc oneshot_read d'ESP-IDF v5.4 compilés sans modification pour la cible esp32 sur l'ESP32-DevKitC V4, de la bannière de la ROM jusqu'à esp_restart ; l'image ROM est identique octet pour octet à l'esp32-v3-rom.bin du QEMU d'Espressif, et la vue qu'en a l'APP_CPU à son esp32-v3-rom-app.bin.", + "claims": "Les deux cœurs Xtensa LX6 (mcu/xtensa/lx6.c, lx6fp.c) tels que les configure le core-isa.h d'Espressif : les instructions de base et de densité, les registres fenêtrés (64, le contrôle de débordement de chaque instruction, CALL4/8/12, ENTRY, RETW et son sous-débordement, MOVSP, ROTW, L32E/S32E), les boucles sans surcoût, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, les booléens, S32C1I avec SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), le paquet MAC16, la FPU simple précision (modes d'arrondi et drapeaux IEEE ; les séquences de division et de racine carrée qu'exécute libgcc donnent des résultats correctement arrondis), les exceptions XEA2 avec vecteurs relocalisables, 32 interruptions aux niveaux et types de core-isa.h (niveau, front, logicielles, les temporisateurs CCOMPARE0-2 contre CCOUNT, NMI), RSIL, WAITI, RFI, BREAK, IBREAK, DBREAK et ICOUNT de l'option de débogage, la protection par régions (huit régions de 512 Mo, 15 sans accès), les chargements et écritures non alignés, et les accès de données en 32 bits seulement du bus d'instructions. Les deux cœurs partagent la machine, l'APP_CPU maintenu en reset jusqu'à ce que DPORT le libère et voyant la ROM avec ses pointeurs vers les données ROM du PRO_CPU déplacés vers les siennes, comme dans l'image ROM APP_CPU d'Espressif. Le démarrage : la ROM d'Espressif (esp32_rev300_rom.elf, révision v3) depuis le vecteur de reset, sa bannière, les broches de configuration (GPIO_STRAP : SPI_FAST_FLASH_BOOT, ou le mode téléchargement avec GPIO0 bas), les causes de reset (RTC_CNTL), le contrôle anti-glitch de l'eFuse, la lecture de la flash par SPI1, le bootloader de second niveau d'ESP-IDF et sa vérification d'image sur l'accélérateur SHA, la MMU flash des caches, et l'application sur les deux cœurs. Les modèles : DPORT (reset, horloge et blocage de l'APP_CPU, CPU_PER_CONF, vidage et état des caches, tables de la MMU flash, les 69 sources de la matrice d'interruptions, les interruptions inter-CPU), RTC_CNTL (causes de reset, le temporisateur RTC, le chien de garde RTC avec sa protection au démarrage sur flash, les resets et blocages logiciels, les registres de stockage, SOC_CLK_SEL), APB_CTRL (les horloges CPU et APB depuis XTAL, PLL ou RC_FAST), le contrôleur eFuse avec les bits d'usine de la puce (ESP32-D0WD-V3 v3.1, une MAC avec son CRC, ou celle des réglages de la carte), GPIO et IO_MUX (40 plots, la matrice, tirages, drain ouvert, interruptions sur chaque CPU, le verrou de configuration), UART0-2 (FIFO, trames au débit, interruptions, les adresses de MEM_RX_STATUS d'après lesquelles ESP-IDF compte les octets en attente), TIMG0/1 (T0, T1, le temporisateur LAC, le MWDT avec sa protection au démarrage sur flash, la calibration de RTC_SLOW_CLK), SPI0/SPI1 avec la flash Giantec GT25Q32A du module (C4 60 16, l'identifiant que rapporte la carte du propriétaire), l'accélérateur SHA (SHA-1, -256, -384, -512), LEDC (huit canaux MLI rapides et huit lents sur huit temporisateurs, le fondu, sur ledc_hs/ls_sig_out de la matrice), I2C0 et I2C1 (maître et esclave sur les lignes à drain ouvert à travers la matrice, les 16 commandes avec RSTART, WRITE, READ, STOP et END, le timing du bus en cycles d'APB_CLK, les contrôles d'ACK, l'arbitrage, le délai d'attente sur SCL, les FIFO de 32 octets par DATA et sa vue AHB, les adresses esclaves sur 7 et 10 bits ; l'exemple i2c_eeprom d'ESP-IDF fonctionne avec une 24LC256), le RMT (huit canaux qui émettent ou reçoivent à travers la matrice, des blocs de 64 mots de la RAM de 512 mots, les marqueurs de fin, le mode continu, le rebouclage, le seuil de TX_LIM, la porteuse, le seuil d'inactivité et le filtre du récepteur, la RAM par CHnDATA ; le led_strip d'ESP-IDF allume une WS2812B), les ADC SAR tels que les pilote le contrôleur RTC de SENS (une conversion à la fois sur les huit plots de l'ADC1 et les dix de l'ADC2, les atténuations, 9 à 12 bits, la sortie inversée du convertisseur et DATA_INV, la durée de la conversion ; la caractéristique est la droite que la calibration Vref d'ESP-IDF ajuste pour la puce, avec son Vref d'eFuse), les resets de PERIP_RST_EN pour I2C0, I2C1 et LEDC, le maître I2C analogique interne, le générateur de nombres aléatoires et la vue AHB des périphériques. Les preuves : les programmes de test_lx6.c assemblés par l'assembleur d'Espressif (entiers, décalages, multiplication et division, boucles, branchements, mémoire avec S32C1I, récursion fenêtrée à travers les vecteurs de fenêtre de la ROM, SYSCALL, l'interruption du temporisateur CCOMPARE, FPU, MAC16) et les données ROM de l'APP_CPU tenues à part ; hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo, real_time_stats, blink avec son ruban de LED et adc oneshot_read d'ESP-IDF v5.4 compilés sans modification pour la cible esp32 sur l'ESP32-DevKitC V4, de la bannière de la ROM jusqu'à esp_restart ; les croquis d'Arduino-ESP32 3.2.0 pour « ESP32 Dev Module » (AnalogReadSerial, ReadAnalogVoltage, DigitalReadSerial, GetChipID, RepeatTimer, avec les points d'arrêt de fin de pile de FreeRTOS actifs) ; l'image ROM est identique octet pour octet à l'esp32-v3-rom.bin du QEMU d'Espressif, et la vue qu'en a l'APP_CPU à son esp32-v3-rom-app.bin.", "omissions": [ "le Wi-Fi, le Bluetooth et l'étage RF (WIFI_MAC, BB, NRX, FE : leurs registres gardent ce qui y est écrit)", "les instructions de l'accélérateur double précision (F64ADDC, F64ITER et les autres : leurs encodages sont publics, pas ce qu'elles font ; un programme qui en exécute une s'arrête avec un diagnostic), bien que ses registres soient conservés", diff --git a/core/mcu/xtensa/lx6.c b/core/mcu/xtensa/lx6.c index e08dc1f8a..7be442539 100644 --- a/core/mcu/xtensa/lx6.c +++ b/core/mcu/xtensa/lx6.c @@ -301,7 +301,9 @@ static int dbreak_hit(struct lx6 *c, u32 a, int n, int store) u32 ctl = c->dbreakc[i], mask; if (!(ctl & (store ? 0x80000000u : 0x40000000u))) continue; - mask = ~(ctl & 0x3fu); + /* MASK's set bits are the low address bits compared (ESP-IDF's + * 32-byte stack-end watchpoint: 0x20, bits 4:0 ignored) */ + mask = 0xffffffc0u | (ctl & 0x3fu); if (((a ^ c->dbreaka[i]) & mask) == 0 || (((a + (u32)n - 1u) ^ c->dbreaka[i]) & mask) == 0) { if ((c->ps & PS_INTLEVEL) < DEBUG_LEVEL) { diff --git a/docs/STATUS.md b/docs/STATUS.md index 8d3bbb164..59a456689 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -19,7 +19,9 @@ scenarios `esp32_hello_world` (9/9), `esp32_blink` (6/6), reading a 24LC256 at 400 kHz), `esp32_gptimer` (17/17), `esp32_uart_echo` (6/6), `esp32_real_time_stats` (10/10) , `esp32_blink_strip` (7/7, a WS2812B through the RMT) and `esp32_adc_oneshot_read` (6/6, ADC1 and ADC2 -with the eFuse Vref's calibration). Not yet: Wi-Fi and Bluetooth, the +with the eFuse Vref's calibration); Arduino-ESP32's AnalogReadSerial, +ReadAnalogVoltage, DigitalReadSerial, GetChipID and RepeatTimer for "ESP32 +Dev Module" (`tests/vendor/arduino-esp32`, `ESP32_*`). Not yet: Wi-Fi and Bluetooth, the double-precision accelerator's instructions, the peripherals the description's fidelity lists (I2S, SPI2/3, the DACs, ...), sleep. The standalone harness below remains as its own qualification diff --git a/tests/unit/gen_lx6_programs.py b/tests/unit/gen_lx6_programs.py index 7e2bbb162..f20404dad 100644 --- a/tests/unit/gen_lx6_programs.py +++ b/tests/unit/gen_lx6_programs.py @@ -216,6 +216,33 @@ movi a9, 0 wsr.intenable a9 rfe +""", + # a store watchpoint over 32 bytes as ESP-IDF sets one (DBREAKC 0x20: + # address bits 4:0 ignored): a store past it misses, one in it takes + # the debug exception (level 6) before it is done + "dbreak": """ + movi a2, vectors + wsr.vecbase a2 + movi a6, 0 + movi a3, 0x3ffb0040 + wsr.dbreaka0 a3 + movi a4, 0x80000020 + wsr.dbreakc0 a4 + isync + movi a5, 0x55 + s32i a5, a3, 32 # 0x3ffb0060: outside + s32i a5, a3, 28 # 0x3ffb005c: inside + l32i a9, a3, 28 # the store was done after the handler + j . + .align 1024 +vectors: + .org vectors + 0x280 +debug: + addi a6, a6, 1 + rsr.debugcause a7 + movi a8, 0 + wsr.dbreakc0 a8 + rfi 6 """, "float": """ movi a2, 1 diff --git a/tests/unit/lx6_programs.h b/tests/unit/lx6_programs.h index 8326b4dc3..61a897d73 100644 --- a/tests/unit/lx6_programs.h +++ b/tests/unit/lx6_programs.h @@ -295,6 +295,115 @@ static const unsigned char prog_timer[1808] = { 0x70, 0xe8, 0x03, 0x80, 0xe2, 0x03, 0x0c, 0x16, 0x0c, 0x09, 0x90, 0xe4, 0x13, 0x00, 0x30, 0x00, }; +#define PROG_DBREAK_ENTRY 0x4008000Cu +static const unsigned char prog_dbreak[1680] = { + 0x00, 0x04, 0x08, 0x40, 0x40, 0x00, 0xfb, 0x3f, 0x20, 0x00, 0x00, 0x80, 0x21, 0xfd, 0xff, 0x20, + 0xe7, 0x13, 0x0c, 0x06, 0x31, 0xfc, 0xff, 0x30, 0x90, 0x13, 0x41, 0xfb, 0xff, 0x40, 0xa0, 0x13, + 0x00, 0x20, 0x00, 0x5c, 0x55, 0x59, 0x83, 0x59, 0x73, 0x98, 0x73, 0x06, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1b, 0x66, 0x70, 0xe9, 0x03, 0x0c, 0x08, 0x80, 0xa0, 0x13, 0x10, 0x36, 0x00, 0x00, 0x00, 0x00, +}; + #define PROG_FLOAT_ENTRY 0x40080000u static const unsigned char prog_float[47] = { 0x0c, 0x12, 0x20, 0xe0, 0x13, 0x10, 0x20, 0x00, 0x0c, 0x33, 0x00, 0x03, 0xca, 0x0c, 0x54, 0x10, diff --git a/tests/unit/test_lx6.c b/tests/unit/test_lx6.c index 79436eb8a..cffa48c5c 100644 --- a/tests/unit/test_lx6.c +++ b/tests/unit/test_lx6.c @@ -287,6 +287,18 @@ static void test_timer(void) close32(&t); } +static void test_dbreak(void) +{ + t32 t; + if (!OPEN(t, dbreak, DBREAK)) + return; + CHECK(run_to_end(&t, 200)); + CHECK_EQ(a(&t, 6), 1u); /* one hit: the store inside */ + CHECK_EQ(a(&t, 7) & 0x4u, 0x4u); /* DEBUGCAUSE: DBREAK */ + CHECK_EQ(a(&t, 9), 0x55u); /* the store done after it */ + close32(&t); +} + static void test_float(void) { t32 t; @@ -345,6 +357,7 @@ RUN(test_memory); RUN(test_windows); RUN(test_syscall); RUN(test_timer); +RUN(test_dbreak); RUN(test_float); RUN(test_mac16); RUN(test_app_cpu_rom_data); diff --git a/tests/vendor/arduino-esp32/examples/ESP32_AnalogReadSerial/project.json b/tests/vendor/arduino-esp32/examples/ESP32_AnalogReadSerial/project.json new file mode 100644 index 000000000..3a70b1874 --- /dev/null +++ b/tests/vendor/arduino-esp32/examples/ESP32_AnalogReadSerial/project.json @@ -0,0 +1,78 @@ +{ + "schema_version": 1, + "kind": "project", + "name": "arduino-esp32-ESP32_AnalogReadSerial", + "description": "AnalogReadSerial on the ESP32-DevKitC V4: A0 is GPIO36 (the header's VP), fed 1.1 V from a source.", + "policy": "electrical", + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc", + "layout": { + "x": 360, + "y": 120 + }, + "firmware": { + "image": "flash.bin", + "format": "bin" + } + } + ], + "components": [ + { + "id": "USB", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5 + }, + "layout": { + "x": 120, + "y": 160 + } + }, + { + "id": "GND1", + "component": "emblink/gnd", + "layout": { + "x": 120, + "y": 320 + } + }, + { + "id": "V1", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 1.1 + }, + "layout": { + "x": 120, + "y": 460 + } + } + ], + "nets": { + "GND": [ + "USB.-", + "GND1.GND", + "B1.GND1", + "V1.-" + ], + "USB5V": [ + "USB.+", + "B1.VBUS" + ], + "A0": [ + "V1.+", + "B1.VP" + ] + }, + "probes": [ + { + "id": "console", + "target": "serial:B1", + "kind": "serial" + } + ] +} \ No newline at end of file diff --git a/tests/vendor/arduino-esp32/examples/ESP32_AnalogReadSerial/scenario.json b/tests/vendor/arduino-esp32/examples/ESP32_AnalogReadSerial/scenario.json new file mode 100644 index 000000000..76299928b --- /dev/null +++ b/tests/vendor/arduino-esp32/examples/ESP32_AnalogReadSerial/scenario.json @@ -0,0 +1,18 @@ +{ + "schema_version": 1, + "kind": "scenario", + "name": "ESP32_AnalogReadSerial", + "until": "1200ms", + "description": "AnalogReadSerial on the ESP32: analogRead(A0) on GPIO36 at Arduino-ESP32's defaults (12 bits, 11 dB), 1.1 V reading 1118 as on ESP-IDF's oneshot_read (the line ESP-IDF's Vref calibration fits for this chip, Vref 1170 mV), printed every millisecond.", + "inputs": [], + "assertions": [ + { + "id": "reads", + "at": "1s", + "probe": "console", + "expect": { + "contains": "\r\n1118\r\n1118\r\n1118\r\n" + } + } + ] +} \ No newline at end of file diff --git a/tests/vendor/arduino-esp32/examples/ESP32_DigitalReadSerial/project.json b/tests/vendor/arduino-esp32/examples/ESP32_DigitalReadSerial/project.json new file mode 100644 index 000000000..0fba094a7 --- /dev/null +++ b/tests/vendor/arduino-esp32/examples/ESP32_DigitalReadSerial/project.json @@ -0,0 +1,90 @@ +{ + "schema_version": 1, + "kind": "project", + "name": "arduino-esp32-ESP32_DigitalReadSerial", + "description": "DigitalReadSerial as its comment wires it: a push button from 3V3 to IO2 (pin 2), 10 kohm from IO2 to ground. On the ESP32-DevKitC V4 (pin 2 is GPIO2).", + "policy": "electrical", + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc", + "layout": { + "x": 360, + "y": 120 + }, + "firmware": { + "image": "flash.bin", + "format": "bin" + } + } + ], + "components": [ + { + "id": "USB", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5 + }, + "layout": { + "x": 120, + "y": 160 + } + }, + { + "id": "GND1", + "component": "emblink/gnd", + "layout": { + "x": 120, + "y": 320 + } + }, + { + "id": "SW1", + "component": "emblink/push_button", + "layout": { + "x": 120, + "y": 460 + } + }, + { + "id": "R1", + "component": "emblink/resistor", + "params": { + "resistance": 10000 + }, + "layout": { + "x": 260, + "y": 460 + } + } + ], + "nets": { + "USB5V": [ + "USB.+", + "B1.VBUS" + ], + "GND": [ + "USB.-", + "GND1.GND", + "B1.GND1", + "R1.2" + ], + "3V3": [ + "B1.3V3", + "SW1.1" + ], + "BTN": [ + "SW1.2", + "B1.IO2", + "R1.1" + ] + }, + "probes": [ + { + "id": "console", + "target": "serial:B1", + "kind": "serial" + } + ] +} \ No newline at end of file diff --git a/tests/vendor/arduino-esp32/examples/ESP32_DigitalReadSerial/scenario.json b/tests/vendor/arduino-esp32/examples/ESP32_DigitalReadSerial/scenario.json new file mode 100644 index 000000000..614c6b9dc --- /dev/null +++ b/tests/vendor/arduino-esp32/examples/ESP32_DigitalReadSerial/scenario.json @@ -0,0 +1,47 @@ +{ + "schema_version": 1, + "kind": "scenario", + "name": "ESP32_DigitalReadSerial", + "until": "2000ms", + "description": "DigitalReadSerial: the button's state printed at 9600 baud, a line a millisecond or as fast as the UART takes it: 0 until the button is pressed at 1 s, 1 while it is held.", + "inputs": [ + { + "at": "1s", + "target": "SW1", + "input": "pressed", + "value": true + }, + { + "at": "1500ms", + "target": "SW1", + "input": "pressed", + "value": false + } + ], + "assertions": [ + { + "id": "released", + "at": "900ms", + "probe": "console", + "expect": { + "contains": "\r\n0\r\n0\r\n" + } + }, + { + "id": "not-yet", + "at": "900ms", + "probe": "console", + "expect": { + "not_contains": "\r\n1\r\n" + } + }, + { + "id": "pressed", + "at": "1450ms", + "probe": "console", + "expect": { + "contains": "\r\n1\r\n1\r\n1\r\n" + } + } + ] +} \ No newline at end of file diff --git a/tests/vendor/arduino-esp32/examples/ESP32_GetChipID/project.json b/tests/vendor/arduino-esp32/examples/ESP32_GetChipID/project.json new file mode 100644 index 000000000..a9df5493f --- /dev/null +++ b/tests/vendor/arduino-esp32/examples/ESP32_GetChipID/project.json @@ -0,0 +1,61 @@ +{ + "schema_version": 1, + "kind": "project", + "name": "arduino-esp32-ESP32_GetChipID", + "description": "GetChipID on the ESP32-DevKitC V4, plugged into USB; Serial is UART0 through the CP2102N.", + "policy": "electrical", + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc", + "layout": { + "x": 360, + "y": 120 + }, + "firmware": { + "image": "flash.bin", + "format": "bin" + } + } + ], + "components": [ + { + "id": "USB", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5 + }, + "layout": { + "x": 120, + "y": 160 + } + }, + { + "id": "GND1", + "component": "emblink/gnd", + "layout": { + "x": 120, + "y": 320 + } + } + ], + "nets": { + "GND": [ + "USB.-", + "GND1.GND", + "B1.GND1" + ], + "USB5V": [ + "USB.+", + "B1.VBUS" + ] + }, + "probes": [ + { + "id": "console", + "target": "serial:B1", + "kind": "serial" + } + ] +} \ No newline at end of file diff --git a/tests/vendor/arduino-esp32/examples/ESP32_GetChipID/scenario.json b/tests/vendor/arduino-esp32/examples/ESP32_GetChipID/scenario.json new file mode 100644 index 000000000..4b56af140 --- /dev/null +++ b/tests/vendor/arduino-esp32/examples/ESP32_GetChipID/scenario.json @@ -0,0 +1,34 @@ +{ + "schema_version": 1, + "kind": "scenario", + "name": "ESP32_GetChipID", + "until": "3600ms", + "description": "GetChipID on the ESP32: the chip as ESP.getChipModel, getChipRevision and getChipCores report it (ESP32-D0WD-V3, 301 for v3.1, two cores) and the ESP8266-style ID, the eFuse MAC's last three bytes (24:0A:C4:45:6D:62: 0x456D62), printed again after 3 s. Serial.printf's \\n goes out alone.", + "inputs": [], + "assertions": [ + { + "id": "model", + "at": "1s", + "probe": "console", + "expect": { + "contains": "ESP32 Chip model = ESP32-D0WD-V3 Rev 301\nThis chip has 2 cores\nChip ID: 4549986\r\n" + } + }, + { + "id": "once", + "at": "1s", + "probe": "console", + "expect": { + "not_contains": "4549986\r\nESP32" + } + }, + { + "id": "again", + "at": "3500ms", + "probe": "console", + "expect": { + "contains": "Chip ID: 4549986\r\nESP32 Chip model = ESP32-D0WD-V3 Rev 301\n" + } + } + ] +} \ No newline at end of file diff --git a/tests/vendor/arduino-esp32/examples/ESP32_ReadAnalogVoltage/project.json b/tests/vendor/arduino-esp32/examples/ESP32_ReadAnalogVoltage/project.json new file mode 100644 index 000000000..0378b2579 --- /dev/null +++ b/tests/vendor/arduino-esp32/examples/ESP32_ReadAnalogVoltage/project.json @@ -0,0 +1,78 @@ +{ + "schema_version": 1, + "kind": "project", + "name": "arduino-esp32-ESP32_ReadAnalogVoltage", + "description": "ReadAnalogVoltage on the ESP32-DevKitC V4: A0 is GPIO36 (the header's VP), fed 1.1 V from a source.", + "policy": "electrical", + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc", + "layout": { + "x": 360, + "y": 120 + }, + "firmware": { + "image": "flash.bin", + "format": "bin" + } + } + ], + "components": [ + { + "id": "USB", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5 + }, + "layout": { + "x": 120, + "y": 160 + } + }, + { + "id": "GND1", + "component": "emblink/gnd", + "layout": { + "x": 120, + "y": 320 + } + }, + { + "id": "V1", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 1.1 + }, + "layout": { + "x": 120, + "y": 460 + } + } + ], + "nets": { + "GND": [ + "USB.-", + "GND1.GND", + "B1.GND1", + "V1.-" + ], + "USB5V": [ + "USB.+", + "B1.VBUS" + ], + "A0": [ + "V1.+", + "B1.VP" + ] + }, + "probes": [ + { + "id": "console", + "target": "serial:B1", + "kind": "serial" + } + ] +} \ No newline at end of file diff --git a/tests/vendor/arduino-esp32/examples/ESP32_ReadAnalogVoltage/scenario.json b/tests/vendor/arduino-esp32/examples/ESP32_ReadAnalogVoltage/scenario.json new file mode 100644 index 000000000..8bde6d32c --- /dev/null +++ b/tests/vendor/arduino-esp32/examples/ESP32_ReadAnalogVoltage/scenario.json @@ -0,0 +1,18 @@ +{ + "schema_version": 1, + "kind": "scenario", + "name": "ESP32_ReadAnalogVoltage", + "until": "1200ms", + "description": "ReadAnalogVoltage on the ESP32: the sketch converts as for the Uno (value x 5.0 / 1023), so the ESP32's 12-bit 1118 for 1.1 V prints 5.46, as it does on a real ESP32.", + "inputs": [], + "assertions": [ + { + "id": "reads", + "at": "1s", + "probe": "console", + "expect": { + "contains": "\r\n5.46\r\n5.46\r\n5.46\r\n" + } + } + ] +} \ No newline at end of file diff --git a/tests/vendor/arduino-esp32/examples/ESP32_RepeatTimer/project.json b/tests/vendor/arduino-esp32/examples/ESP32_RepeatTimer/project.json new file mode 100644 index 000000000..300352de6 --- /dev/null +++ b/tests/vendor/arduino-esp32/examples/ESP32_RepeatTimer/project.json @@ -0,0 +1,61 @@ +{ + "schema_version": 1, + "kind": "project", + "name": "arduino-esp32-ESP32_RepeatTimer", + "description": "RepeatTimer on the ESP32-DevKitC V4: BTN_STOP_ALARM is GPIO0, the board's own Boot button (SW1).", + "policy": "electrical", + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc", + "layout": { + "x": 360, + "y": 120 + }, + "firmware": { + "image": "flash.bin", + "format": "bin" + } + } + ], + "components": [ + { + "id": "USB", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5 + }, + "layout": { + "x": 120, + "y": 160 + } + }, + { + "id": "GND1", + "component": "emblink/gnd", + "layout": { + "x": 120, + "y": 320 + } + } + ], + "nets": { + "GND": [ + "USB.-", + "GND1.GND", + "B1.GND1" + ], + "USB5V": [ + "USB.+", + "B1.VBUS" + ] + }, + "probes": [ + { + "id": "console", + "target": "serial:B1", + "kind": "serial" + } + ] +} \ No newline at end of file diff --git a/tests/vendor/arduino-esp32/examples/ESP32_RepeatTimer/scenario.json b/tests/vendor/arduino-esp32/examples/ESP32_RepeatTimer/scenario.json new file mode 100644 index 000000000..ab30ee778 --- /dev/null +++ b/tests/vendor/arduino-esp32/examples/ESP32_RepeatTimer/scenario.json @@ -0,0 +1,47 @@ +{ + "schema_version": 1, + "kind": "scenario", + "name": "ESP32_RepeatTimer", + "until": "6s", + "description": "RepeatTimer: a timer at 1 MHz with a 1 s auto-reload alarm, its ISR giving a semaphore the loop prints from, with millis() as the ISR saw it; the board's Boot button (IO0) pressed at 3.5 s ends the timer, so there is no fourth. The alarms come at 1006, 2006 and 3006 ms of millis(): setup starts the timer 6 ms after the clock millis() reads began, later than on the C3.", + "inputs": [ + { + "at": "3500ms", + "target": "B1.SW1", + "input": "pressed", + "value": true + }, + { + "at": "3700ms", + "target": "B1.SW1", + "input": "pressed", + "value": false + } + ], + "assertions": [ + { + "id": "first", + "at": "1200ms", + "probe": "console", + "expect": { + "contains": "onTimer no. 1 at 1006 ms\r\n" + } + }, + { + "id": "third", + "at": "3400ms", + "probe": "console", + "expect": { + "contains": "onTimer no. 2 at 2006 ms\r\nonTimer no. 3 at 3006 ms\r\n" + } + }, + { + "id": "stopped", + "at": "6s", + "probe": "console", + "expect": { + "not_contains": "onTimer no. 4" + } + } + ] +} \ No newline at end of file diff --git a/tests/vendor/arduino-esp32/run.py b/tests/vendor/arduino-esp32/run.py index a99c3050f..1f284a934 100644 --- a/tests/vendor/arduino-esp32/run.py +++ b/tests/vendor/arduino-esp32/run.py @@ -2,7 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2026 Junior Deogracias """Arduino sketches built with Espressif's Arduino-ESP32 core, unmodified, on -EmbSIM's ESP32-C3-DevKitM-1 (the RTOS boards plan, part 2, step 7; 01 §8.2). +EmbSIM's ESP32-C3-DevKitM-1 (the RTOS boards plan, part 2, step 7; 01 §8.2) +and, for the examples named ESP32_*, its ESP32-DevKitC V4. The core is not vendored. --cli names arduino-cli and --config its configuration, in whose data directory the esp32 platform (3.2.0, on ESP-IDF @@ -14,7 +15,8 @@ Each example under examples// is one of those sketches (EXAMPLES below names where), compiled as the IDE compiles it for "ESP32C3 Dev Module" (`arduino-cli compile --fqbn esp32:esp32:esp32c3`, the board's defaults: -UART0 as Serial, 4 MB flash in QIO at 80 MHz). The build's merged image +UART0 as Serial, 4 MB flash in QIO at 80 MHz), the ESP32_* ones for "ESP32 +Dev Module" (esp32:esp32:esp32, its defaults). The build's merged image (bootloader, partition table, boot_app0 and the sketch at their offsets, the rest erased) runs examples//project.json, or this directory's, under the example's scenario.json, whose assertions must all pass. @@ -36,10 +38,19 @@ from vendor_run import run_scenario, sh # noqa: E402 FQBN = "esp32:esp32:esp32c3" +# the board an example is compiled for, when not "ESP32C3 Dev Module" (its +# project.json names the EmbSIM board): "ESP32 Dev Module" for the +# ESP32-DevKitC V4's ESP32_ examples +FQBNS = {"ESP32_": "esp32:esp32:esp32"} PLATFORM = "3.2.0" # examples// and its sketch: the IDE's built-in examples ("builtin", # arduino-examples' examples/) or the core's ("core", its libraries/) EXAMPLES = { + "ESP32_AnalogReadSerial": ("builtin", "01.Basics/AnalogReadSerial"), + "ESP32_ReadAnalogVoltage": ("builtin", "01.Basics/ReadAnalogVoltage"), + "ESP32_DigitalReadSerial": ("builtin", "01.Basics/DigitalReadSerial"), + "ESP32_GetChipID": ("core", "ESP32/examples/ChipID/GetChipID"), + "ESP32_RepeatTimer": ("core", "ESP32/examples/Timer/RepeatTimer"), "Blink": ("builtin", "01.Basics/Blink"), "DigitalReadSerial": ("builtin", "01.Basics/DigitalReadSerial"), "Fade": ("builtin", "01.Basics/Fade"), @@ -63,12 +74,16 @@ def platform_dir(cli: str, config: str) -> Path: return d if (d / "platform.txt").exists() else None -def build(cli: str, config: str, sketch: Path, out: Path) -> Path: +def fqbn_of(name: str) -> str: + return next((f for prefix, f in FQBNS.items() if name.startswith(prefix)), FQBN) + + +def build(cli: str, config: str, sketch: Path, out: Path, fqbn: str = FQBN) -> Path: """The sketch compiled as the IDE compiles it: its merged flash image""" - work = out / sketch.name + work = out / fqbn.rsplit(":", 1)[-1] / sketch.name shutil.rmtree(work, ignore_errors=True) shutil.copytree(sketch, work / sketch.name) - sh([cli, "--config-file", config, "compile", "--fqbn", FQBN, "--output-dir", work / "out", work / sketch.name]) + sh([cli, "--config-file", config, "compile", "--fqbn", fqbn, "--output-dir", work / "out", work / sketch.name]) return work / "out" / f"{sketch.name}.ino.merged.bin" @@ -99,7 +114,7 @@ def main() -> int: for name in names: where, path = EXAMPLES[name] sketch = (Path(args.examples) / "examples" if where == "builtin" else core / "libraries") / path - flash = build(args.cli, args.config, sketch, work / "build") + flash = build(args.cli, args.config, sketch, work / "build", fqbn_of(name)) mine = HERE / "examples" / name project = mine / "project.json" if (mine / "project.json").exists() else HERE / "project.json" bad += not run_scenario(Path(args.embsim), project, name, flash, mine / "scenario.json", work / "run", From 3eba631405ddc249f3b42966dd169d2dc2338af3 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Sun, 8 Nov 2026 05:38:20 +0000 Subject: [PATCH 55/57] ESP32: GPIO's pad mask without a truncating cast (MSVC C4310) (u32)PADS cut a 64-bit constant, which MSVC's /W4 /WX rejects; the low word is taken with a mask instead. --- core/mcu/esp32/soc/gpio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/mcu/esp32/soc/gpio.c b/core/mcu/esp32/soc/gpio.c index df156be19..b2941fefd 100644 --- a/core/mcu/esp32/soc/gpio.c +++ b/core/mcu/esp32/soc/gpio.c @@ -406,7 +406,7 @@ static void set_clr(struct esk_rf_reg *r, int set, u32 v, u32 mask) static void gp_write(void *ctx, u32 off, int n, u32 v) { struct e32_gpio *k = (struct e32_gpio *)ctx; - const u32 lo = (u32)PADS, hi = (u32)(PADS >> 32) & 0xffu; + const u32 lo = (u32)(PADS & 0xffffffffull), hi = (u32)(PADS >> 32) & 0xffu; switch (off) { case 0x38: case 0x3C: case 0x40: case 0x60: case 0x64: case 0x68: case 0x6C: return; /* read-only */ From 54f37c8d99d2792d10f5cc9854f9278c12da679a Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Sun, 8 Nov 2026 06:07:02 +0000 Subject: [PATCH 56/57] LX6: MOVF/MOVT compare the boolean as unsigned (MSVC C4389) The branch register's bit is unsigned, the comparison's result an int: MSVC's /W4 /WX rejects the mixed ==. --- core/mcu/xtensa/lx6.c | 2 +- core/mcu/xtensa/lx6fp.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/mcu/xtensa/lx6.c b/core/mcu/xtensa/lx6.c index 7be442539..d2fb215f9 100644 --- a/core/mcu/xtensa/lx6.c +++ b/core/mcu/xtensa/lx6.c @@ -1211,7 +1211,7 @@ static void exec(struct lx6 *c, u32 w, u32 len) case 12: case 13: /* MOVF MOVT */ if (win(c, r > s ? r : s)) return; - if (((c->br >> t) & 1u) == (op2 == 13)) + if (((c->br >> t) & 1u) == (u32)(op2 == 13)) AR(c, r) = AR(c, s); return; case 14: { /* RUR */ diff --git a/core/mcu/xtensa/lx6fp.c b/core/mcu/xtensa/lx6fp.c index 411a11b62..8c3b1786e 100644 --- a/core/mcu/xtensa/lx6fp.c +++ b/core/mcu/xtensa/lx6fp.c @@ -408,7 +408,7 @@ int esk_lx6_fp(struct lx6 *c, u32 w) return 0; } if (op2 == 12 || op2 == 13) { /* MOVF.S MOVT.S */ - if (((esk_lx6_br(c) >> t) & 1u) == (op2 == 13)) + if (((esk_lx6_br(c) >> t) & 1u) == (u32)(op2 == 13)) f[r] = f[s]; return 0; } From 36685afed9c8bccca1fcce9f6323091c961a97d4 Mon Sep 17 00:00:00 2001 From: Junior Deogracias Date: Sun, 8 Nov 2026 06:36:50 +0000 Subject: [PATCH 57/57] ESP32: the DACs, with ESP-IDF's dac_oneshot RTC_IO's PAD_DAC1 and PAD_DAC2: with the pad the RTC IO MUX's and the DAC powered, GPIO25 and GPIO26 carry VDD3P3_RTC x code / 255 (the GPIO model hands the pad to the DAC as an analog output). ESP-IDF's dac/dac_oneshot runs unmodified on the DevKitC, its ADC2 reading both DACs back on their pads (esp32_dac_oneshot 4/4). For that the runtime's pin_volts gives a pin's own DAC voltage when its net has no analog part (it gave the digital level, so an ADC read 0). --- CHANGELOG.md | 6 +- catalog/mcus/espressif/esp32/mcu.json | 17 ++-- core/mcu/esp32/soc/esp32.h | 6 ++ core/mcu/esp32/soc/gpio.c | 6 ++ core/mcu/esp32/soc/rtcio.c | 82 +++++++++++++++++++ core/mcu/mcu.c | 2 + core/mcu/models.c | 1 + docs/STATUS.md | 5 +- docs/coverage.md | 8 +- .../examples/esp32_dac_oneshot/project.json | 61 ++++++++++++++ .../examples/esp32_dac_oneshot/scenario.json | 42 ++++++++++ tests/vendor/esp-idf/run.py | 2 + 12 files changed, 225 insertions(+), 13 deletions(-) create mode 100644 core/mcu/esp32/soc/rtcio.c create mode 100644 tests/vendor/esp-idf/examples/esp32_dac_oneshot/project.json create mode 100644 tests/vendor/esp-idf/examples/esp32_dac_oneshot/scenario.json diff --git a/CHANGELOG.md b/CHANGELOG.md index ea0176189..c51c9a1ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ versions follow semantic versioning. There is no release yet. strapping, UART0-2, TIMG0/1, SPI0/1 with the GT25Q32A flash, SHA, LEDC, I2C0/1 (master and slave, 16 commands, APB_CLK timing; DPORT's PERIP_RST_EN resets them), the RMT's eight channels, the SAR ADCs - through SENS, the internal analog I2C, RNG and the AHB view. + through SENS, the two DACs through RTC_IO, the internal analog I2C, RNG and the AHB view. - Added the `emblink/esp32_devkitc` board (ESP32-DevKitC V4, WROOM-32E) with its power path, LED, Boot and EN buttons, headers and fixtures. - Espressif's ROM, ESP-IDF v5.4's bootloader and hello_world run unmodified @@ -31,7 +31,9 @@ versions follow semantic versioning. There is no release yet. 24LC256, `esp32_gptimer` 17/17, `esp32_uart_echo` 6/6 on UART2, `esp32_real_time_stats` 10/10 across both cores, `esp32_blink_strip` 7/7 on a WS2812B through the RMT, - `esp32_adc_oneshot_read` 6/6 with the eFuse Vref's calibration; + `esp32_adc_oneshot_read` 6/6 with the eFuse Vref's calibration, + `esp32_dac_oneshot` 4/4 (the DACs read back by ADC2); an ADC reads a + pin's own DAC on a net with no analog part (the runtime's pin_volts); Arduino-ESP32's AnalogReadSerial, ReadAnalogVoltage, DigitalReadSerial, GetChipID and RepeatTimer for "ESP32 Dev Module" in `tests/vendor/arduino-esp32` (`ESP32_*`); DBREAKC's MASK now marks the diff --git a/catalog/mcus/espressif/esp32/mcu.json b/catalog/mcus/espressif/esp32/mcu.json index a2f031e7f..d8e4d37bf 100644 --- a/catalog/mcus/espressif/esp32/mcu.json +++ b/catalog/mcus/espressif/esp32/mcu.json @@ -207,6 +207,10 @@ { "match": "SENS", "model": "esp32-sens" + }, + { + "match": "RTC_IO", + "model": "esp32-rtcio" } ], "io": { @@ -275,15 +279,16 @@ }, "fidelity": { "functional": { - "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts, MEM_RX_STATUS's addresses that ESP-IDF counts the bytes waiting from), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), LEDC (eight high-speed and eight low-speed PWM channels on eight timers, fading, on the matrix's ledc_hs/ls_sig_out), I2C0 and I2C1 (master and slave on the open-drain lines through the matrix, the 16 commands with RSTART, WRITE, READ, STOP and END, the bus timing in APB_CLK cycles, ACK checks, arbitration, the SCL timeout, the 32-byte FIFOs through DATA and its AHB view, 7- and 10-bit slave addresses; ESP-IDF's i2c_eeprom runs on a 24LC256), the RMT (eight channels sending or receiving through the matrix, 64-word blocks of the 512-word RAM, end markers, continuous mode, wrap, TX_LIM's threshold, the carrier, the receiver's idle threshold and filter, the RAM through CHnDATA; ESP-IDF's led_strip lights a WS2812B), the SAR ADCs as SENS's RTC controller drives them (one conversion at a time on ADC1's eight and ADC2's ten pads, the attenuations, 9 to 12 bits, the converter's inverted output and DATA_INV, the conversion's time; the transfer the line ESP-IDF's Vref calibration fits for the chip, its eFuse Vref), PERIP_RST_EN's resets of I2C0, I2C1 and LEDC, the internal analog I2C master, the random number generator and the peripherals' AHB view. The evidence: test_lx6.c's programs assembled by Espressif's assembler (integer, shifts, multiply and divide, loops, branches, memory with S32C1I, windowed recursion through the ROM's own window vectors, SYSCALL, the CCOMPARE timer interrupt, FPU, MAC16) and the APP_CPU's ROM data kept apart; ESP-IDF v5.4's hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo, real_time_stats, blink with its LED strip and adc oneshot_read built unmodified for the esp32 target on the ESP32-DevKitC V4, from the ROM's banner to esp_restart; Arduino-ESP32 3.2.0's sketches for \"ESP32 Dev Module\" (AnalogReadSerial, ReadAnalogVoltage, DigitalReadSerial, GetChipID, RepeatTimer, with FreeRTOS's stack-end watchpoints on); the ROM image equals Espressif QEMU's esp32-v3-rom.bin byte for byte, and the APP_CPU's view of it QEMU's esp32-v3-rom-app.bin.", + "claims": "The two Xtensa LX6 cores (mcu/xtensa/lx6.c, lx6fp.c) as Espressif's core-isa.h configures them: the core and density instructions, the windowed registers (64, the overflow check of every instruction, CALL4/8/12, ENTRY, RETW and its underflow, MOVSP, ROTW, L32E/S32E), zero-overhead loops, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, booleans, S32C1I with SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), the MAC16 package, the single-precision FPU (IEEE rounding modes and flags; the division and square root sequences libgcc runs give correctly rounded results), XEA2 exceptions with relocatable vectors, 32 interrupts at core-isa.h's levels and types (level, edge, software, the CCOMPARE0-2 timers against CCOUNT, NMI), RSIL, WAITI, RFI, the debug option's BREAK, IBREAK, DBREAK and ICOUNT, region protection (eight 512 MB regions, 15 no access), unaligned loads and stores, and the instruction bus's 32-bit-only data accesses. Both cores share the machine, the APP_CPU held in reset until DPORT releases it and seeing the ROM with its pointers into the PRO_CPU's ROM data moved to its own, as Espressif's own APP_CPU ROM image has them. The boot: Espressif's ROM (esp32_rev300_rom.elf, chip revision v3) from the reset vector, its banner, the strapping pins (GPIO_STRAP: SPI_FAST_FLASH_BOOT, or the download mode with GPIO0 low), the reset causes (RTC_CNTL), the eFuse glitch check, the flash read through SPI1, ESP-IDF's second-stage bootloader and its image check on the SHA accelerator, the caches' flash MMU, and the application on both cores. The models: DPORT (the APP_CPU's reset, clock and stall, CPU_PER_CONF, the caches' flush and state, the flash MMU tables, the interrupt matrix's 69 sources, the cross-CPU interrupts), RTC_CNTL (reset causes, the RTC timer, the RTC watchdog with its flash-boot protection, the software resets and stalls, the store registers, SOC_CLK_SEL), APB_CTRL (the CPU and APB clocks from XTAL, PLL or RC_FAST), the eFuse controller with the chip's factory bits (ESP32-D0WD-V3 v3.1, a MAC with its CRC, or the board settings'), GPIO and IO_MUX (40 pads, the matrix, pulls, open drain, interrupts on each CPU, the strapping latch), UART0-2 (FIFOs, frames at the baud rate, interrupts, MEM_RX_STATUS's addresses that ESP-IDF counts the bytes waiting from), TIMG0/1 (T0, T1, the LAC timer, the MWDT with its flash-boot protection, the RTC_SLOW_CLK calibration), SPI0/SPI1 with the module's Giantec GT25Q32A flash (C4 60 16, the ID the owner's board reports), the SHA accelerator (SHA-1, -256, -384, -512), LEDC (eight high-speed and eight low-speed PWM channels on eight timers, fading, on the matrix's ledc_hs/ls_sig_out), I2C0 and I2C1 (master and slave on the open-drain lines through the matrix, the 16 commands with RSTART, WRITE, READ, STOP and END, the bus timing in APB_CLK cycles, ACK checks, arbitration, the SCL timeout, the 32-byte FIFOs through DATA and its AHB view, 7- and 10-bit slave addresses; ESP-IDF's i2c_eeprom runs on a 24LC256), the RMT (eight channels sending or receiving through the matrix, 64-word blocks of the 512-word RAM, end markers, continuous mode, wrap, TX_LIM's threshold, the carrier, the receiver's idle threshold and filter, the RAM through CHnDATA; ESP-IDF's led_strip lights a WS2812B), the SAR ADCs as SENS's RTC controller drives them (one conversion at a time on ADC1's eight and ADC2's ten pads, the attenuations, 9 to 12 bits, the converter's inverted output and DATA_INV, the conversion's time; the transfer the line ESP-IDF's Vref calibration fits for the chip, its eFuse Vref), the two DACs on GPIO25 and GPIO26 (RTC_IO's PAD_DAC1/2: powered and the RTC IO MUX's, VDD3P3_RTC x code / 255 on the pad, which ADC2 reads back), PERIP_RST_EN's resets of I2C0, I2C1 and LEDC, the internal analog I2C master, the random number generator and the peripherals' AHB view. The evidence: test_lx6.c's programs assembled by Espressif's assembler (integer, shifts, multiply and divide, loops, branches, memory with S32C1I, windowed recursion through the ROM's own window vectors, SYSCALL, the CCOMPARE timer interrupt, FPU, MAC16) and the APP_CPU's ROM data kept apart; ESP-IDF v5.4's hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo, real_time_stats, blink with its LED strip, adc oneshot_read and dac_oneshot built unmodified for the esp32 target on the ESP32-DevKitC V4, from the ROM's banner to esp_restart; Arduino-ESP32 3.2.0's sketches for \"ESP32 Dev Module\" (AnalogReadSerial, ReadAnalogVoltage, DigitalReadSerial, GetChipID, RepeatTimer, with FreeRTOS's stack-end watchpoints on); the ROM image equals Espressif QEMU's esp32-v3-rom.bin byte for byte, and the APP_CPU's view of it QEMU's esp32-v3-rom-app.bin.", "omissions": [ "Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written)", "the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept", "PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts", "RMT: the memory's power-down (MEM_PD), STATUS's STATE and the APB-side error bits, the carrier on an idle output", "the SAR ADCs: the nonlinearity above about 2450 mV at 11 dB and a chip's spread from the fitted line, noise, the ULP's and the digital controller's (DMA, I2S) conversions, the Hall sensor, ADC2's arbitration with Wi-Fi, the RTC IO MUX's routing of a pad to the ADC (not checked)", + "the DACs: the cosine-wave generator and the DMA path (I2S0), the output's impedance (a stiff source: the datasheet gives none) and its offset and nonlinearity", "I2C: the non-FIFO mode (NONFIFO_EN, RX_REC_FULL, TX_SEND_EMPTY), SAMPLE_SCL_LEVEL, the input filters (only their share of the SCL high time), 10-bit addressing's R/W check", - "the peripherals without a model yet: I2S0/1, SPI2/3 (HSPI, VSPI), MCPWM, PCNT, TWAI, SDMMC, EMAC, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption", + "the peripherals without a model yet: I2S0/1, SPI2/3 (HSPI, VSPI), MCPWM, PCNT, TWAI, SDMMC, EMAC, the touch sensor, the RTC IO MUX's other pads and the ULP coprocessor, the AES and RSA accelerators, flash encryption", "sleep modes (light and deep sleep, the power domains), brown-out", "the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection" ], @@ -299,6 +304,7 @@ "repo:tests/vendor/esp-idf/examples/esp32_real_time_stats/scenario.json", "repo:tests/vendor/esp-idf/examples/esp32_blink_strip/scenario.json", "repo:tests/vendor/esp-idf/examples/esp32_adc_oneshot_read/scenario.json", + "repo:tests/vendor/esp-idf/examples/esp32_dac_oneshot/scenario.json", "repo:tests/vendor/arduino-esp32/run.py" ] }, @@ -405,19 +411,20 @@ }, "i18n": { "fr": { - "source": "a076e310391c", + "source": "97aa22899d11", "description": "Espressif ESP32 (ESP32-D0WD-V3, révision de puce v3.1) : deux cœurs Xtensa LX6 jusqu'à 240 MHz, 520 Kio de SRAM, 448 Kio de ROM, 16 Kio de mémoire RTC, une flash SPI externe derrière les caches des cœurs, Wi-Fi et Bluetooth ; le MCU de l'ESP32-DevKitC. La ROM d'Espressif, le bootloader de second niveau d'ESP-IDF et les applications ESP-IDF tournent sur les deux cœurs ; les radios ne sont pas encore modélisées.", "fidelity": { "functional": { - "claims": "Les deux cœurs Xtensa LX6 (mcu/xtensa/lx6.c, lx6fp.c) tels que les configure le core-isa.h d'Espressif : les instructions de base et de densité, les registres fenêtrés (64, le contrôle de débordement de chaque instruction, CALL4/8/12, ENTRY, RETW et son sous-débordement, MOVSP, ROTW, L32E/S32E), les boucles sans surcoût, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, les booléens, S32C1I avec SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), le paquet MAC16, la FPU simple précision (modes d'arrondi et drapeaux IEEE ; les séquences de division et de racine carrée qu'exécute libgcc donnent des résultats correctement arrondis), les exceptions XEA2 avec vecteurs relocalisables, 32 interruptions aux niveaux et types de core-isa.h (niveau, front, logicielles, les temporisateurs CCOMPARE0-2 contre CCOUNT, NMI), RSIL, WAITI, RFI, BREAK, IBREAK, DBREAK et ICOUNT de l'option de débogage, la protection par régions (huit régions de 512 Mo, 15 sans accès), les chargements et écritures non alignés, et les accès de données en 32 bits seulement du bus d'instructions. Les deux cœurs partagent la machine, l'APP_CPU maintenu en reset jusqu'à ce que DPORT le libère et voyant la ROM avec ses pointeurs vers les données ROM du PRO_CPU déplacés vers les siennes, comme dans l'image ROM APP_CPU d'Espressif. Le démarrage : la ROM d'Espressif (esp32_rev300_rom.elf, révision v3) depuis le vecteur de reset, sa bannière, les broches de configuration (GPIO_STRAP : SPI_FAST_FLASH_BOOT, ou le mode téléchargement avec GPIO0 bas), les causes de reset (RTC_CNTL), le contrôle anti-glitch de l'eFuse, la lecture de la flash par SPI1, le bootloader de second niveau d'ESP-IDF et sa vérification d'image sur l'accélérateur SHA, la MMU flash des caches, et l'application sur les deux cœurs. Les modèles : DPORT (reset, horloge et blocage de l'APP_CPU, CPU_PER_CONF, vidage et état des caches, tables de la MMU flash, les 69 sources de la matrice d'interruptions, les interruptions inter-CPU), RTC_CNTL (causes de reset, le temporisateur RTC, le chien de garde RTC avec sa protection au démarrage sur flash, les resets et blocages logiciels, les registres de stockage, SOC_CLK_SEL), APB_CTRL (les horloges CPU et APB depuis XTAL, PLL ou RC_FAST), le contrôleur eFuse avec les bits d'usine de la puce (ESP32-D0WD-V3 v3.1, une MAC avec son CRC, ou celle des réglages de la carte), GPIO et IO_MUX (40 plots, la matrice, tirages, drain ouvert, interruptions sur chaque CPU, le verrou de configuration), UART0-2 (FIFO, trames au débit, interruptions, les adresses de MEM_RX_STATUS d'après lesquelles ESP-IDF compte les octets en attente), TIMG0/1 (T0, T1, le temporisateur LAC, le MWDT avec sa protection au démarrage sur flash, la calibration de RTC_SLOW_CLK), SPI0/SPI1 avec la flash Giantec GT25Q32A du module (C4 60 16, l'identifiant que rapporte la carte du propriétaire), l'accélérateur SHA (SHA-1, -256, -384, -512), LEDC (huit canaux MLI rapides et huit lents sur huit temporisateurs, le fondu, sur ledc_hs/ls_sig_out de la matrice), I2C0 et I2C1 (maître et esclave sur les lignes à drain ouvert à travers la matrice, les 16 commandes avec RSTART, WRITE, READ, STOP et END, le timing du bus en cycles d'APB_CLK, les contrôles d'ACK, l'arbitrage, le délai d'attente sur SCL, les FIFO de 32 octets par DATA et sa vue AHB, les adresses esclaves sur 7 et 10 bits ; l'exemple i2c_eeprom d'ESP-IDF fonctionne avec une 24LC256), le RMT (huit canaux qui émettent ou reçoivent à travers la matrice, des blocs de 64 mots de la RAM de 512 mots, les marqueurs de fin, le mode continu, le rebouclage, le seuil de TX_LIM, la porteuse, le seuil d'inactivité et le filtre du récepteur, la RAM par CHnDATA ; le led_strip d'ESP-IDF allume une WS2812B), les ADC SAR tels que les pilote le contrôleur RTC de SENS (une conversion à la fois sur les huit plots de l'ADC1 et les dix de l'ADC2, les atténuations, 9 à 12 bits, la sortie inversée du convertisseur et DATA_INV, la durée de la conversion ; la caractéristique est la droite que la calibration Vref d'ESP-IDF ajuste pour la puce, avec son Vref d'eFuse), les resets de PERIP_RST_EN pour I2C0, I2C1 et LEDC, le maître I2C analogique interne, le générateur de nombres aléatoires et la vue AHB des périphériques. Les preuves : les programmes de test_lx6.c assemblés par l'assembleur d'Espressif (entiers, décalages, multiplication et division, boucles, branchements, mémoire avec S32C1I, récursion fenêtrée à travers les vecteurs de fenêtre de la ROM, SYSCALL, l'interruption du temporisateur CCOMPARE, FPU, MAC16) et les données ROM de l'APP_CPU tenues à part ; hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo, real_time_stats, blink avec son ruban de LED et adc oneshot_read d'ESP-IDF v5.4 compilés sans modification pour la cible esp32 sur l'ESP32-DevKitC V4, de la bannière de la ROM jusqu'à esp_restart ; les croquis d'Arduino-ESP32 3.2.0 pour « ESP32 Dev Module » (AnalogReadSerial, ReadAnalogVoltage, DigitalReadSerial, GetChipID, RepeatTimer, avec les points d'arrêt de fin de pile de FreeRTOS actifs) ; l'image ROM est identique octet pour octet à l'esp32-v3-rom.bin du QEMU d'Espressif, et la vue qu'en a l'APP_CPU à son esp32-v3-rom-app.bin.", + "claims": "Les deux cœurs Xtensa LX6 (mcu/xtensa/lx6.c, lx6fp.c) tels que les configure le core-isa.h d'Espressif : les instructions de base et de densité, les registres fenêtrés (64, le contrôle de débordement de chaque instruction, CALL4/8/12, ENTRY, RETW et son sous-débordement, MOVSP, ROTW, L32E/S32E), les boucles sans surcoût, MUL16/MULL/MULUH/MULSH, QUOS/QUOU/REMS/REMU, NSA, MIN/MAX, SEXT, CLAMPS, les booléens, S32C1I avec SCOMPARE1, THREADPTR, MISC0-3, PRID (0xCDCD, 0xABAB), le paquet MAC16, la FPU simple précision (modes d'arrondi et drapeaux IEEE ; les séquences de division et de racine carrée qu'exécute libgcc donnent des résultats correctement arrondis), les exceptions XEA2 avec vecteurs relocalisables, 32 interruptions aux niveaux et types de core-isa.h (niveau, front, logicielles, les temporisateurs CCOMPARE0-2 contre CCOUNT, NMI), RSIL, WAITI, RFI, BREAK, IBREAK, DBREAK et ICOUNT de l'option de débogage, la protection par régions (huit régions de 512 Mo, 15 sans accès), les chargements et écritures non alignés, et les accès de données en 32 bits seulement du bus d'instructions. Les deux cœurs partagent la machine, l'APP_CPU maintenu en reset jusqu'à ce que DPORT le libère et voyant la ROM avec ses pointeurs vers les données ROM du PRO_CPU déplacés vers les siennes, comme dans l'image ROM APP_CPU d'Espressif. Le démarrage : la ROM d'Espressif (esp32_rev300_rom.elf, révision v3) depuis le vecteur de reset, sa bannière, les broches de configuration (GPIO_STRAP : SPI_FAST_FLASH_BOOT, ou le mode téléchargement avec GPIO0 bas), les causes de reset (RTC_CNTL), le contrôle anti-glitch de l'eFuse, la lecture de la flash par SPI1, le bootloader de second niveau d'ESP-IDF et sa vérification d'image sur l'accélérateur SHA, la MMU flash des caches, et l'application sur les deux cœurs. Les modèles : DPORT (reset, horloge et blocage de l'APP_CPU, CPU_PER_CONF, vidage et état des caches, tables de la MMU flash, les 69 sources de la matrice d'interruptions, les interruptions inter-CPU), RTC_CNTL (causes de reset, le temporisateur RTC, le chien de garde RTC avec sa protection au démarrage sur flash, les resets et blocages logiciels, les registres de stockage, SOC_CLK_SEL), APB_CTRL (les horloges CPU et APB depuis XTAL, PLL ou RC_FAST), le contrôleur eFuse avec les bits d'usine de la puce (ESP32-D0WD-V3 v3.1, une MAC avec son CRC, ou celle des réglages de la carte), GPIO et IO_MUX (40 plots, la matrice, tirages, drain ouvert, interruptions sur chaque CPU, le verrou de configuration), UART0-2 (FIFO, trames au débit, interruptions, les adresses de MEM_RX_STATUS d'après lesquelles ESP-IDF compte les octets en attente), TIMG0/1 (T0, T1, le temporisateur LAC, le MWDT avec sa protection au démarrage sur flash, la calibration de RTC_SLOW_CLK), SPI0/SPI1 avec la flash Giantec GT25Q32A du module (C4 60 16, l'identifiant que rapporte la carte du propriétaire), l'accélérateur SHA (SHA-1, -256, -384, -512), LEDC (huit canaux MLI rapides et huit lents sur huit temporisateurs, le fondu, sur ledc_hs/ls_sig_out de la matrice), I2C0 et I2C1 (maître et esclave sur les lignes à drain ouvert à travers la matrice, les 16 commandes avec RSTART, WRITE, READ, STOP et END, le timing du bus en cycles d'APB_CLK, les contrôles d'ACK, l'arbitrage, le délai d'attente sur SCL, les FIFO de 32 octets par DATA et sa vue AHB, les adresses esclaves sur 7 et 10 bits ; l'exemple i2c_eeprom d'ESP-IDF fonctionne avec une 24LC256), le RMT (huit canaux qui émettent ou reçoivent à travers la matrice, des blocs de 64 mots de la RAM de 512 mots, les marqueurs de fin, le mode continu, le rebouclage, le seuil de TX_LIM, la porteuse, le seuil d'inactivité et le filtre du récepteur, la RAM par CHnDATA ; le led_strip d'ESP-IDF allume une WS2812B), les ADC SAR tels que les pilote le contrôleur RTC de SENS (une conversion à la fois sur les huit plots de l'ADC1 et les dix de l'ADC2, les atténuations, 9 à 12 bits, la sortie inversée du convertisseur et DATA_INV, la durée de la conversion ; la caractéristique est la droite que la calibration Vref d'ESP-IDF ajuste pour la puce, avec son Vref d'eFuse), les deux DAC sur GPIO25 et GPIO26 (PAD_DAC1/2 de RTC_IO : alimentés et confiés à l'IO MUX RTC, VDD3P3_RTC x code / 255 sur le plot, que l'ADC2 relit), les resets de PERIP_RST_EN pour I2C0, I2C1 et LEDC, le maître I2C analogique interne, le générateur de nombres aléatoires et la vue AHB des périphériques. Les preuves : les programmes de test_lx6.c assemblés par l'assembleur d'Espressif (entiers, décalages, multiplication et division, boucles, branchements, mémoire avec S32C1I, récursion fenêtrée à travers les vecteurs de fenêtre de la ROM, SYSCALL, l'interruption du temporisateur CCOMPARE, FPU, MAC16) et les données ROM de l'APP_CPU tenues à part ; hello_world, blink, ledc_basic, i2c_eeprom, gptimer, uart_echo, real_time_stats, blink avec son ruban de LED, adc oneshot_read et dac_oneshot d'ESP-IDF v5.4 compilés sans modification pour la cible esp32 sur l'ESP32-DevKitC V4, de la bannière de la ROM jusqu'à esp_restart ; les croquis d'Arduino-ESP32 3.2.0 pour « ESP32 Dev Module » (AnalogReadSerial, ReadAnalogVoltage, DigitalReadSerial, GetChipID, RepeatTimer, avec les points d'arrêt de fin de pile de FreeRTOS actifs) ; l'image ROM est identique octet pour octet à l'esp32-v3-rom.bin du QEMU d'Espressif, et la vue qu'en a l'APP_CPU à son esp32-v3-rom-app.bin.", "omissions": [ "le Wi-Fi, le Bluetooth et l'étage RF (WIFI_MAC, BB, NRX, FE : leurs registres gardent ce qui y est écrit)", "les instructions de l'accélérateur double précision (F64ADDC, F64ITER et les autres : leurs encodages sont publics, pas ce qu'elles font ; un programme qui en exécute une s'arrête avec un diagnostic), bien que ses registres soient conservés", "la PSRAM (DRAM1), le remappage de démarrage, les permissions d'accès APB/AHB, les régions verrouillées et le timing des caches (les pages mappées sont lues entières), les interruptions d'erreur d'accès de la MMU", "RMT : la mise hors tension de la mémoire (MEM_PD), le champ STATE de STATUS et les bits d'erreur côté APB, la porteuse sur une sortie inactive", "les ADC SAR : la non-linéarité au-delà d'environ 2450 mV à 11 dB et l'écart d'une puce à la droite ajustée, le bruit, les conversions de l'ULP et du contrôleur numérique (DMA, I2S), le capteur à effet Hall, l'arbitrage de l'ADC2 avec le Wi-Fi, le routage d'un plot vers l'ADC par l'IO MUX RTC (non vérifié)", + "les DAC : le générateur d'onde cosinus et la voie DMA (I2S0), l'impédance de sortie (une source idéale : la fiche technique n'en donne pas), leur décalage et leur non-linéarité", "I2C : le mode hors FIFO (NONFIFO_EN, RX_REC_FULL, TX_SEND_EMPTY), SAMPLE_SCL_LEVEL, les filtres d'entrée (seulement leur part du temps haut de SCL), le contrôle R/W de l'adressage sur 10 bits", - "les périphériques sans modèle pour l'instant : I2S0/1, SPI2/3 (HSPI, VSPI), MCPWM, PCNT, TWAI, SDMMC, EMAC, les DAC, le capteur tactile, l'IO MUX RTC et le coprocesseur ULP, les accélérateurs AES et RSA, le chiffrement de la flash", + "les périphériques sans modèle pour l'instant : I2S0/1, SPI2/3 (HSPI, VSPI), MCPWM, PCNT, TWAI, SDMMC, EMAC, le capteur tactile, les autres plots de l'IO MUX RTC et le coprocesseur ULP, les accélérateurs AES et RSA, le chiffrement de la flash", "les modes de veille (veille légère et profonde, les domaines d'alimentation), la détection de baisse de tension", "le contrôle du schéma de codage 3/4 de l'eFuse, la protection en écriture par champ du BLOCK0" ] diff --git a/core/mcu/esp32/soc/esp32.h b/core/mcu/esp32/soc/esp32.h index 3604387de..fdb5d6f75 100644 --- a/core/mcu/esp32/soc/esp32.h +++ b/core/mcu/esp32/soc/esp32.h @@ -20,6 +20,7 @@ * i2c.c I2C0 and I2C1 * rmt.c the remote control peripheral's eight channels * adc.c the SAR ADCs as SENS's RTC controller drives them + * rtcio.c the RTC IO MUX's DACs * misc.c the random number generator, the AHB view of the * peripherals */ #ifndef ESK_MCU_ESP32_SOC_H @@ -61,6 +62,11 @@ extern const struct esk_dev_ops esk_esp32_rmt_ops; void *esk_esp32_rmt_create(struct esk_mach *s, struct esk_rf_periph *p); extern const struct esk_dev_ops esk_esp32_adc_ops; void *esk_esp32_adc_create(struct esk_mach *s, struct esk_rf_periph *p); +extern const struct esk_dev_ops esk_esp32_rtcio_ops; +void *esk_esp32_rtcio_create(struct esk_mach *s, struct esk_rf_periph *p); +/* DAC1 on GPIO25, DAC2 on GPIO26 (rtcio.c): 1 and the voltage it drives + * behind its output resistance, or 0 when the pad is not the DAC's */ +int esk_esp32_dac_out(struct esk_mach *m, int gpio, double *volts, double *rout); extern const struct esk_dev_ops esk_esp32_sha_ops; void *esk_esp32_sha_create(struct esk_mach *s, struct esk_rf_periph *p); /* the RNG and the AHB view of the peripherals (misc.c): 0, or -1 */ diff --git a/core/mcu/esp32/soc/gpio.c b/core/mcu/esp32/soc/gpio.c index b2941fefd..89be81ebc 100644 --- a/core/mcu/esp32/soc/gpio.c +++ b/core/mcu/esp32/soc/gpio.c @@ -265,6 +265,12 @@ static void settle(struct e32_gpio *k) o.pull = wpu && !wpd ? 1 : wpd && !wpu ? 2 : 0; o.af = af || (mux_sel(k, n) == 0 && n == 3) /* U0RXD */ || (rx >> n & 1u); o.analog = !(mux & MUX_FUN_IE) && !oe; + if (esk_esp32_dac_out(s, n, &o.volts, &o.aout_r)) { /* a DAC's pad */ + o.aout = 1; + o.analog = 1; + o.drive = -1; + drive = 0; + } if (s->host.pin_cfg && (!k->known || memcmp(&k->cfg[n], &o, sizeof o) != 0)) s->host.pin_cfg(s->host.ctx, n / 16, n % 16, &o); k->cfg[n] = o; diff --git a/core/mcu/esp32/soc/rtcio.c b/core/mcu/esp32/soc/rtcio.c new file mode 100644 index 000000000..62087a8c5 --- /dev/null +++ b/core/mcu/esp32/soc/rtcio.c @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright 2026 Junior Deogracias */ +/* rtcio.c -- the ESP32's RTC IO MUX (RTC_IO, TRM v5 chapter 4.11) as far as + * its two DACs (chapter 29.5): DAC1 on GPIO25 (PAD_DAC1), DAC2 on GPIO26 + * (PAD_DAC2). + * + * - While the pad is the RTC IO MUX's (MUX_SEL) and its DAC powered + * (XPD_DAC, with DAC_XPD_FORCE as ESP-IDF sets it), the pad is the DAC's + * output: VDD3P3_RTC x DAC / 255 for the eight-bit code in PDACn_DAC, + * the relation ESP-IDF's dac_oneshot documents; the GPIO model gives the + * pad that voltage (gpio.c). + * - Writes to the registers settle the pads again. + * Where it differs inside: the output is a stiff source (the datasheet gives + * no output impedance). Not yet: the cosine-wave generator (SENS's + * SAR_DAC_CTRL1/2: SW_TONE_EN, DAC_CW_EN1/2, the scale, phase and offset) + * and the DMA path (DAC_DIG_FORCE, I2S0), whose outputs read as the code + * written; the other RTC pads' functions, pulls and hold. */ +#include + +#include "mcu/esp32/soc/esp32.h" + +#define PAD_DAC1 0x84u +#define PAD_DAC2 0x88u +#define XPD_DAC (1u << 18) +#define MUX_SEL (1u << 17) + +struct e32_rtcio { + struct esk_mach *s; + struct esk_rf_periph *p; +}; + +static struct e32_rtcio *rtcio_of(struct esk_mach *m) +{ + return m->rf ? (struct e32_rtcio *)esk_rf_ctx_of(m->rf, &esk_esp32_rtcio_ops) : 0; +} + +int esk_esp32_dac_out(struct esk_mach *m, int gpio, double *volts, double *rout) +{ + struct e32_rtcio *k = gpio == 25 || gpio == 26 ? rtcio_of(m) : 0; + u32 v; + double vdd; + if (!k) + return 0; + v = esk_rf_read(k->p, gpio == 25 ? PAD_DAC1 : PAD_DAC2, 4); + if (!(v & XPD_DAC) || !(v & MUX_SEL)) + return 0; + vdd = m->vdd > 0.0 ? m->vdd : 3.3; + *volts = vdd * (double)((v >> 19) & 0xffu) / 255.0; + *rout = 1.0; + return 1; +} + +static u32 rio_read(void *ctx, u32 off, int n) +{ + struct e32_rtcio *k = (struct e32_rtcio *)ctx; + return esk_rf_read(k->p, off, n); +} + +static void rio_write(void *ctx, u32 off, int n, u32 v) +{ + struct e32_rtcio *k = (struct e32_rtcio *)ctx; + esk_rf_write(k->p, off, n, v); + if ((off & ~3u) == PAD_DAC1 || (off & ~3u) == PAD_DAC2) + esk_esp32_pins_changed(k->s); +} + +static void rio_reset(void *ctx) { (void)ctx; } + +const struct esk_dev_ops esk_esp32_rtcio_ops = { + "esp32-rtcio", rio_read, rio_write, rio_reset, 0, 0, +}; + +void *esk_esp32_rtcio_create(struct esk_mach *s, struct esk_rf_periph *p) +{ + struct e32_rtcio *k = (struct e32_rtcio *)esk_mach_alloc(s, sizeof *k); + if (!k) + return 0; + memset(k, 0, sizeof *k); + k->s = s; + k->p = p; + return k; +} diff --git a/core/mcu/mcu.c b/core/mcu/mcu.c index 08a3404d5..05464c286 100644 --- a/core/mcu/mcu.c +++ b/core/mcu/mcu.c @@ -773,6 +773,8 @@ static int h_pin_volts(void *ctx, int port, int bit, double *v) n = p->net; if (n->island) *v = esk_net_voltage_now(p->net); /* an ADC samples it now */ + else if (st->pq[port][bit].cfg.aout) + *v = st->pq[port][bit].cfg.volts; /* the pin's own DAC, alone on its net */ else *v = esk_net_value(n) == ESK_L1 ? st->m.vdd : 0.0; return 1; diff --git a/core/mcu/models.c b/core/mcu/models.c index a1fbe3149..b7cfbea77 100644 --- a/core/mcu/models.c +++ b/core/mcu/models.c @@ -75,6 +75,7 @@ static const struct esk_rf_model_type model_types[] = { { "esp32-i2c", &esk_esp32_i2c_ops, esk_esp32_i2c_create }, { "esp32-rmt", &esk_esp32_rmt_ops, esk_esp32_rmt_create }, { "esp32-sens", &esk_esp32_adc_ops, esk_esp32_adc_create }, + { "esp32-rtcio", &esk_esp32_rtcio_ops, esk_esp32_rtcio_create }, { "esp32-iomux", &esk_esp32_iomux_ops, esk_esp32_iomux_create }, { "esp32c3-intmtx", &esk_esp32c3_intmtx_ops, esk_esp32c3_intmtx_create }, { "esp32c3-uart", &esk_esp32c3_uart_ops, esk_esp32c3_uart_create }, diff --git a/docs/STATUS.md b/docs/STATUS.md index 59a456689..4ea1a62bc 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -19,11 +19,12 @@ scenarios `esp32_hello_world` (9/9), `esp32_blink` (6/6), reading a 24LC256 at 400 kHz), `esp32_gptimer` (17/17), `esp32_uart_echo` (6/6), `esp32_real_time_stats` (10/10) , `esp32_blink_strip` (7/7, a WS2812B through the RMT) and `esp32_adc_oneshot_read` (6/6, ADC1 and ADC2 -with the eFuse Vref's calibration); Arduino-ESP32's AnalogReadSerial, +with the eFuse Vref's calibration), `esp32_dac_oneshot` (4/4, the DACs +read back by ADC2); Arduino-ESP32's AnalogReadSerial, ReadAnalogVoltage, DigitalReadSerial, GetChipID and RepeatTimer for "ESP32 Dev Module" (`tests/vendor/arduino-esp32`, `ESP32_*`). Not yet: Wi-Fi and Bluetooth, the double-precision accelerator's instructions, the peripherals the -description's fidelity lists (I2S, SPI2/3, the DACs, ...), +description's fidelity lists (I2S, SPI2/3, touch, ...), sleep. The standalone harness below remains as its own qualification fixtures. diff --git a/docs/coverage.md b/docs/coverage.md index dd3902425..ad9dc481a 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -8,7 +8,7 @@ evidence for each model is in [STATUS.md](STATUS.md). | MCU | Modelled | Registers only | Missing | Peripherals | |---|---|---|---|---| -| ESP32 | 19 | 0 | 26 | 45 | +| ESP32 | 20 | 0 | 25 | 45 | | ESP32-C3 | 35 | 0 | 0 | 35 | | ATmega328P | 15 | 0 | 0 | 15 | | PIC24FJ1024GB610 | 11 | 4 | 17 | 32 | @@ -23,7 +23,7 @@ evidence for each model is in [STATUS.md](STATUS.md). ## ESP32 (`espressif/esp32`) Boards: ESP32-DevKitC V4. -19 of 45 peripherals modelled, 0 registers only, 26 missing. +20 of 45 peripherals modelled, 0 registers only, 25 missing. | Peripheral | Model | State | |---|---|---| @@ -39,7 +39,7 @@ Boards: ESP32-DevKitC V4. | FLASH_ENCRYPTION | — | **missing** | | FRC_TIMER | — | **missing** | | RTC_CNTL | `esp32-rtc` | modelled | -| RTC_IO | — | **missing** | +| RTC_IO | `esp32-rtcio` | modelled | | SENS | `esp32-sens` | modelled | | RTC_I2C | — | **missing** | | IO_MUX | `esp32-iomux` | modelled | @@ -73,7 +73,7 @@ Boards: ESP32-DevKitC V4. | I2S1 | — | **missing** | | UART2 | `esp32-uart` | modelled | -Declared omissions: Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written); the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept; PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts; RMT: the memory's power-down (MEM_PD), STATUS's STATE and the APB-side error bits, the carrier on an idle output; the SAR ADCs: the nonlinearity above about 2450 mV at 11 dB and a chip's spread from the fitted line, noise, the ULP's and the digital controller's (DMA, I2S) conversions, the Hall sensor, ADC2's arbitration with Wi-Fi, the RTC IO MUX's routing of a pad to the ADC (not checked); I2C: the non-FIFO mode (NONFIFO_EN, RX_REC_FULL, TX_SEND_EMPTY), SAMPLE_SCL_LEVEL, the input filters (only their share of the SCL high time), 10-bit addressing's R/W check; the peripherals without a model yet: I2S0/1, SPI2/3 (HSPI, VSPI), MCPWM, PCNT, TWAI, SDMMC, EMAC, the DACs, the touch sensor, the RTC IO MUX and the ULP coprocessor, the AES and RSA accelerators, flash encryption; sleep modes (light and deep sleep, the power domains), brown-out; the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection. +Declared omissions: Wi-Fi, Bluetooth and the RF front end (WIFI_MAC, BB, NRX, FE: their registers keep what is written); the double-precision accelerator's instructions (F64ADDC, F64ITER and the others: their encodings are public, not what they do; a program that runs one stops with a diagnostic), though its registers are kept; PSRAM (DRAM1), the boot remap, the APB/AHB access permissions, the caches' lock regions and timing (the mapped pages are read whole), the MMU's access-error interrupts; RMT: the memory's power-down (MEM_PD), STATUS's STATE and the APB-side error bits, the carrier on an idle output; the SAR ADCs: the nonlinearity above about 2450 mV at 11 dB and a chip's spread from the fitted line, noise, the ULP's and the digital controller's (DMA, I2S) conversions, the Hall sensor, ADC2's arbitration with Wi-Fi, the RTC IO MUX's routing of a pad to the ADC (not checked); the DACs: the cosine-wave generator and the DMA path (I2S0), the output's impedance (a stiff source: the datasheet gives none) and its offset and nonlinearity; I2C: the non-FIFO mode (NONFIFO_EN, RX_REC_FULL, TX_SEND_EMPTY), SAMPLE_SCL_LEVEL, the input filters (only their share of the SCL high time), 10-bit addressing's R/W check; the peripherals without a model yet: I2S0/1, SPI2/3 (HSPI, VSPI), MCPWM, PCNT, TWAI, SDMMC, EMAC, the touch sensor, the RTC IO MUX's other pads and the ULP coprocessor, the AES and RSA accelerators, flash encryption; sleep modes (light and deep sleep, the power domains), brown-out; the eFuse 3/4 coding scheme's check, BLOCK0's per-field write protection. ## ESP32-C3 (`espressif/esp32c3`) diff --git a/tests/vendor/esp-idf/examples/esp32_dac_oneshot/project.json b/tests/vendor/esp-idf/examples/esp32_dac_oneshot/project.json new file mode 100644 index 000000000..ba2de2ed0 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_dac_oneshot/project.json @@ -0,0 +1,61 @@ +{ + "schema_version": 1, + "kind": "project", + "name": "esp-idf-esp32_dac_oneshot", + "description": "ESP-IDF's dac/dac_oneshot on the ESP32-DevKitC V4: DAC1 on IO25 and DAC2 on IO26, read back by ADC2's channels 8 and 9 on the same pads, plugged into USB.", + "policy": "electrical", + "boards": [ + { + "id": "B1", + "board": "emblink/esp32_devkitc", + "layout": { + "x": 360, + "y": 120 + }, + "firmware": { + "image": "flash.bin", + "format": "bin" + } + } + ], + "components": [ + { + "id": "USB", + "component": "emblink/vsource", + "params": { + "kind": "dc", + "dc": 5 + }, + "layout": { + "x": 120, + "y": 160 + } + }, + { + "id": "GND1", + "component": "emblink/gnd", + "layout": { + "x": 120, + "y": 320 + } + } + ], + "nets": { + "GND": [ + "USB.-", + "GND1.GND", + "B1.GND1" + ], + "USB5V": [ + "USB.+", + "B1.VBUS" + ] + }, + "probes": [ + { + "id": "console", + "target": "serial:B1", + "kind": "serial" + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/examples/esp32_dac_oneshot/scenario.json b/tests/vendor/esp-idf/examples/esp32_dac_oneshot/scenario.json new file mode 100644 index 000000000..32a438554 --- /dev/null +++ b/tests/vendor/esp-idf/examples/esp32_dac_oneshot/scenario.json @@ -0,0 +1,42 @@ +{ + "schema_version": 1, + "kind": "scenario", + "name": "esp32_dac_oneshot", + "until": "3s", + "description": "dac/dac_oneshot on the ESP32: each DAC steps its code by 10 every 500 ms (DAC2 half a second behind), VDD3P3_RTC x code / 255 on its pad, and ADC2 (channels 8 and 9, GPIO25 and GPIO26, 12 dB) reads the pads every 100 ms. With this chip's Vref (1170 mV) ADC2's fitted line is a = 56320, b = 128 mV: code 10 (129.4 mV) reads 2, 20 reads 152, 30 reads 303, 40 reads 453, 50 (647.1 mV) reads 604. The example prints with a bare \\n.", + "inputs": [], + "assertions": [ + { + "id": "started", + "at": "1s", + "probe": "console", + "expect": { + "contains": "DAC channel 0 value: 2\tDAC channel 1 value: 0\n" + } + }, + { + "id": "steps", + "at": "2900ms", + "probe": "console", + "expect": { + "contains": "DAC channel 0 value: 152\tDAC channel 1 value: 2\n" + } + }, + { + "id": "steps2", + "at": "2900ms", + "probe": "console", + "expect": { + "contains": "DAC channel 0 value: 453\tDAC channel 1 value: 303\n" + } + }, + { + "id": "code50", + "at": "2900ms", + "probe": "console", + "expect": { + "contains": "DAC channel 0 value: 604\tDAC channel 1 value: 453\n" + } + } + ] +} \ No newline at end of file diff --git a/tests/vendor/esp-idf/run.py b/tests/vendor/esp-idf/run.py index 534bea08a..b07671e86 100644 --- a/tests/vendor/esp-idf/run.py +++ b/tests/vendor/esp-idf/run.py @@ -80,6 +80,7 @@ "esp32_real_time_stats": "examples/system/freertos/real_time_stats", "esp32_blink_strip": "examples/get-started/blink", "esp32_adc_oneshot_read": "examples/peripherals/adc/oneshot_read", + "esp32_dac_oneshot": "examples/peripherals/dac/dac_oneshot", } # the target an example is built for, when not the ESP32-C3 (its board in # examples//project.json), and the toolchain that target needs @@ -93,6 +94,7 @@ "esp32_real_time_stats": "esp32", "esp32_blink_strip": "esp32", "esp32_adc_oneshot_read": "esp32", + "esp32_dac_oneshot": "esp32", } TOOLCHAINS = {"esp32c3": "riscv32-esp-elf-gcc", "esp32": "xtensa-esp32-elf-gcc"} # examples built only for another's project (with examples//'s