Skip to content

feat(esp32s3): add PSRAM support - #5554

Open
digitalentity wants to merge 2 commits into
tinygo-org:devfrom
helvionics:de_esp32s3_psram
Open

feat(esp32s3): add PSRAM support#5554
digitalentity wants to merge 2 commits into
tinygo-org:devfrom
helvionics:de_esp32s3_psram

Conversation

@digitalentity

@digitalentity digitalentity commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

PSRAM support for ESP32-S3. Enabled via numa_psram_qspi and numa_psram_octal build tags.

  • OPI PSRAM was tested on ESP32-S3-WROOM-1U-N16R8
  • QSPI PSRAM not tested yet (I don't have the hardware)

Test on QSPI board with: ./build/tinygo flash -size=short -serial=uart -target=esp32s3-psram-qspi -monitor -panic=trap examples/psram

Test on OPI board with: ./build/tinygo flash -size=short -serial=uart -target=esp32s3-psram-octal -monitor -panic=trap examples/psram, should be getting something like this:

=== PSRAM Example ===
psramBuffer start address: 0x3D000000
psramBuffer size:          1048576 bytes
Writing test pattern to PSRAM...
Verifying test pattern...
SUCCESS: PSRAM 1MB read/write verification passed!

Partly fixes #3772

@digitalentity
digitalentity marked this pull request as draft July 24, 2026 22:00
@digitalentity
digitalentity marked this pull request as ready for review July 30, 2026 21:05
@digitalentity

digitalentity commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

I am removing QSPI support for now. The boards I got also have PSRAM connected via Octo-SPI. Apparently all ESP32-S3 boards with 8M PSRAM use OctoSPI.

OctoSPI is verified on 2 different ESP32-S3 boards now.

@fleshin

fleshin commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

works fine with XIAO ESP32S3

@digitalentity

Copy link
Copy Markdown
Contributor Author

@deadprogram I believe this is ready

@rdon-key

rdon-key commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

#5567 changes how ESP32-S3 IROM/DROM are placed in flash and mapped through the MMU, in order to support larger flash images. This PR also changes the ESP32-S3 MMU and linker layout for PSRAM, and as far as I can tell it still assumes the current flash mapping (see src/runtime/runtime_esp32_psram.go).

Since both touch the same memory-layout area, @deadprogram would it make sense to settle #5567 first, and then review this one against the resulting layout? Happy to help with the rebase either way.

@digitalentity does that match your reading, or do you think the two are independent enough to review separately?

@digitalentity

Copy link
Copy Markdown
Contributor Author

@rdon-key I suspect this PR might fix the large image boot issue as well. Could you please check? I'll look at the #5567 in the meantime.

@rdon-key

Copy link
Copy Markdown
Contributor

@digitalentity

I tested #5554 with the same large-DROM reproduction case used for #5567.

It still fails in the ROM bootloader before TinyGo startup is reached:

load:0x3c000020,len:0x780104
Invalid image block, can't boot.

So #5554 does not appear to fix the large image boot issue. #5567 still seems to be needed to change how the IROM/DROM segments are laid out in the ESP image.

@digitalentity

Copy link
Copy Markdown
Contributor Author

@rdon-key how do you test? Is it sufficient to put 1M data into the DROM to make it fail? I suspect it's merely the image layout issue. The #5567 is still needed to fix it, but I'm wondering if changes to the linker script and the image builder would be sufficient.

I'll try blending our changes together and see what happens.

@rdon-key

Copy link
Copy Markdown
Contributor

@digitalentity
I used exactly the reproduction case documented in #5567: a deterministic 7.5 MiB file embedded with //go:embed into DROM, flashed to an M5Stamp-S3A.

I haven't tested the exact minimum failing size separately. It appears to be around 1 MiB. If you're testing it, I'd also try a case near the flash limit.

@digitalentity

Copy link
Copy Markdown
Contributor Author

@rdon-key apparently your entire change is necessary to support large image boot. I'll have to figure out how to change the MMU mapping to support PSRAM on top of it.

@rdon-key

Copy link
Copy Markdown
Contributor

@digitalentity
Thanks for digging into this. That lines up with what I found in my testing as well.

Using #5567 as the base and adapting the PSRAM MMU mapping on top of it sounds good to me. Once you have a combined branch, I'm happy to test the large-image case again on the M5Stamp-S3A.

Add runtime initialization, MMU layout, linker script mappings, and targets for ESP32-S3 Octal PSRAM.
The ROM bootloader rejects an image whose DROM segment exceeds 1MB, so
any application with more than 1MB of read-only data failed to boot.

DROM and IROM are now kept out of the ROM-loaded segment table entirely
and appended to the image at 64KB-aligned flash offsets. Those offsets
are patched into .data before the checksum and hash are computed, and
the startup code programs the flash cache MMU from them, which decouples
virtual pages from flash pages.

Because the ESP32-S3 IBUS and DBUS windows share one 512-entry MMU
table, .text and .rodata must occupy disjoint linear ranges. The linker
script now places .rodata one whole page past the end of .text and sizes
the DROM region so that an overlarge link fails at link time instead of
silently colliding with the PSRAM window.

Co-Authored-By: Konstantin Sharlaimov <konstantin.sharlaimov@gmail.com>
@digitalentity

Copy link
Copy Markdown
Contributor Author

The ac70d2f now adds fixes from #5567 with some refactoring.

I also took the opportunity to refactor the builder for less code duplication and better readability.

This is not yet tested on the real hardware.

@digitalentity

digitalentity commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

This PR now absorbes the #5567.

@rdon-key @deadprogram, the change is tested and confirmed to boot large images correctly on a generic ESP32-S3 and ESP32-S3 w/ PSRAM (esp32s3-psram-octal target). PSRAM support is also verified to work.

Thanks a lot @rdon-key for fixing the large image boot. This will be extremely useful once we also have #5556 and will need to store high-res sprites in the image itself.

@rdon-key

Copy link
Copy Markdown
Contributor

@digitalentity Nice work getting the large-image case booting.

One concern: the plan here was to use #5567 as the base and adapt the PSRAM MMU mapping on top of it. Absorbing #5567 into this PR, together with the builder refactor, makes the scope significantly larger and harder to review.

This now combines PSRAM support, the IROM/DROM layout fix, and a builder refactor in one PR. Keeping those changes separate would make review and testing easier, and regressions much easier to isolate.

My preference would still be #5567 first, then rebase the PSRAM work on top of it.

@deadprogram thoughts?

@deadprogram

Copy link
Copy Markdown
Member

Here are some generated comments regarding the latest changes:

Reviewed locally by fetching the branch, building tinygo from it, ran the tests, and compared generated images against the merge base (7a9c649).

Main concern: every S3 image grows ~124 KB. Same examples/blinky1, identical code size (6998 B), but the .bin goes 7,088 B → 131,272 B on esp32s3-supermini, xiao-esp32s3 and esp32s3-box-3 alike. It's all zero padding — 96% of the resulting image. Two gaps: the RAM image ends at 0x980 so IROM pads up to 0x10000 (~62 KB), then IROM ends at 0x11168 so DROM pads up to 0x20000 (~59 KB). Flash time is unaffected (deflate takes it back to ~4.6 KB, and the flasher erases the whole chip anyway), but ~124 KB of occupied flash per image is significant on 2 MB modules and for anyone laying out partitions.

This looks avoidable: base got zero padding from the .text_dummy/.rodata_dummy trick — note its VMAs are deliberately not page-aligned (.rodata at 0x3c000020, .text at 0x42000a20) so each region's low 16 bits match its flash offset. That's compatible with this PR's design — keep the VMA low-16 equal to the flash-offset low-16 and have _irom_flash_addr/_drom_flash_addr point at the containing page's base. The MMU maps whole pages regardless, unreferenced bytes at the head of a page are harmless, and IROM/DROM still occupy disjoint linear ranges. That should reclaim both gaps without giving up the >1 MB DROM fix.

Also worth noting: the flash-segment rework touches all esp32s3 targets, not just PSRAM ones, so I'd want boot confirmation on a couple of plain S3 boards before merge.

Correctness items:

  1. .psram isn't in the GC root set (_globals_start/_globals_end = _sbss/_edata), so a //go:section .psram variable holding a pointer or slice lets the GC free what it points at.
  2. zeroInitPSRAM writes _spsram.._epsram without checking it fits the detected chip density.
  3. The Cache_Dbus_MMU_Set error panic runs while DCache is disabled, and its string lives in .rodata behind that cache.
  4. initPSRAM() runs before machine.InitSerial(), so its panic messages are lost.
  5. Docs/tags reference a QSPI driver (runtime_esp32_psram_qspi.go, numa_psram_qspi, -target=esp32s3-psram-qspi) that isn't in the PR.

Verified working: go test ./builder -run TestESP32S3 passes 8/8; image layout, checksum, SHA256 and the patched _irom_flash_addr/_drom_flash_addr all check out; and I dumped calibratePSRAMTiming's literal pool to confirm it really is flash-free while the cache is off (only stray reference is a never-taken runtime.lookupPanic from bounds checks).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

How to use esp32 psram?

4 participants