FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

stm32: Fix STM32N6 ethernet RX performance and MPU initialization. by kwagyeman · Pull Request #19625 · micropython/micropython · GitHub

stm32: Fix STM32N6 ethernet RX performance and MPU initialization. - #19625

Open
kwagyeman wants to merge 4 commits into
micropython:masterfrom
kwagyeman:kwabena/stm32n6_eth_perf
Open

stm32: Fix STM32N6 ethernet RX performance and MPU initialization.#19625
kwagyeman wants to merge 4 commits into
micropython:masterfrom
kwagyeman:kwabena/stm32n6_eth_perf

Conversation

kwagyeman commented Aug 15, 2026
edited
Loading

Copy link
Copy Markdown
Contributor

Summary

Sustained TCP receive on the STM32N6 tops out at ~50Mbit/s on a 1Gbit RGMII
link (OpenMV N6), while TCP transmit runs at ~620Mbit/s on the same setup.
The bottleneck is the RX DMA: the 4KB MTL RX FIFO overflows (the MTL RX
queue overflow counter increments) even while free RX descriptors are
available, i.e. the DMA cannot drain the FIFO at line rate.

Three eth changes, one commit each, all scoped to STM32N6:

  1. Program the RX DMA burst length. DMACxRXCR.RXPBL was left at its
    reset value of 0, so the RX DMA moves frames out of the FIFO in minimal
    bursts. The TX path already programs a burst length. Set RX to 32 beats.
  2. Raise the AXI outstanding-request limits. The DMASBMR read/write
    outstanding-request limit fields reset to one outstanding AXI request,
    which serialises the RX DMA's memory writes. Raise both to the maximum.
  3. Skip redundant software RX checksum verification. The N6 MAC is
    configured with checksum offload (MACCR.IPC), and the MTL — in
    store-and-forward mode with forward-error-packets left clear — drops
    checksum-errored frames before the driver sees them. lwip was
    nevertheless re-verifying every RX checksum in software, and since this
    driver runs lwip's input path inside the ETH IRQ handler, that work
    happens at interrupt priority for every frame. Keep only the software
    ICMP checks on STM32N6. Other MCUs don't enable MACCR.IPC and are
    unchanged.

Each register fix was first validated by poking the register live from
Python and watching the MTL overflow counter and throughput respond, then
implemented and re-measured.

A fourth commit fixes a boot-reliability bug found while validating the
above on hardware: some OPENMV_N6 builds of current master fail to boot,
parked in MemManage_Handler before reaching main() with CFSR=IACCVIOL, and
whether a given build boots depends on its code layout. Root cause: this
board boots via a bootloader (mboot or OpenMV's — both supported per the
board's mpconfigboard.mk), and both hand control to the application with
the MPU still enabled (mboot calls mpu_init() itself and
branch_to_application() never disables it; the OpenMV bootloader was
observed handing over with CTRL=ENABLE|HFNMIENA|PRIVDEFENA). mpu_init()
then reprograms MAIR0, region 0 and CTRL while the MPU is live and code is
executing from external XSPI flash — an instruction fetch landing inside
that reconfiguration window takes a spurious MemManage on the Cortex-M55.
The fix disables the MPU (with barriers) and clears every region before
configuring it, so initialization starts from a defined state.

Performance on an OpenMV N6 (1Gbit RGMII link, Python socket benchmark
with preallocated buffers and 64KB readinto, same board/host/switch,
8 runs across two build layouts):

master this PR
TCP RX sustained ~50 Mbit/s 102–150 Mbit/s (median ~120, ~2.4×)
TCP TX sustained ~620 Mbit/s unchanged
UDP RX, paced ≤200 Mbit/s ~0.5% loss unchanged
UDP TX ~490 Mbit/s, 0% loss unchanged

Testing

Tested on an OpenMV N6 (STM32N657, RGMII, 1Gbit link) running this
branch's OPENMV_N6 build: Python socket benchmarks for TCP/UDP in both
directions (numbers above), plus hardware-counter verification that the
MTL RX FIFO overflow counter stops incrementing under sustained TCP RX
after the register changes.

The MPU fix was verified by flashing a master build that faults on every
boot (MemManage before main) and confirming the same source with this fix
boots cleanly across repeated reset cycles; the full branch was also
boot-cycled and re-benchmarked on top of the fix.

An STM32H747 board (Arduino Portenta H7, 100Mbit RMII link) was
runtime-tested with the same benchmark suite: ~line rate (TCP RX 91.1 /
TCP TX 80.9 / UDP TX 87.5 / UDP RX ~95 Mbit/s), identical with and without
the checksum change — consistent with the H7 not being RX-CPU-bound at
100Mbit. The eth changes are all N6-scoped: the H7 (like F4/F7) does not
enable MACCR.IPC, so its software checksum verification is retained.

Build-tested: OPENMV_N6, NUCLEO_H563ZI (shares the ARMv8-M
mpu_init()), NUCLEO_H743ZI, NUCLEO_F767ZI (the second eth commit
restructures a #if shared with the H5/H7 path).

Trade-offs and Alternatives

No RAM and negligible code-size cost — the eth changes are register
configuration and a compile-time lwip checksum setting; the MPU change
adds a short clear-all-regions loop at init.

The remaining gap to line rate on TCP RX is the RX ring depth: lwip's
input path runs under the lwip lock (which masks the ETH IRQ during
Python-side socket operations), and when the 16-entry RX ring fills during
those windows the FIFO overflows again. Deepening the ring from 16 to 48
entries was measured at ~234 Mbit/s TCP RX (~4.7×) but costs 48KB of RAM,
so it is deliberately not included; it could be exposed later as a
board-configurable option (e.g. MICROPY_HW_ETH_RX_BUF_NUM) for boards
willing to spend the RAM.

The MPU commit is independent of the eth commits and can be split into its
own PR if preferred.

Generative AI

I used generative AI tools when creating this PR, but a human has checked
the code and is responsible for the code and the description above.

The RX programmable burst length was left at its reset value of zero, so
the RX DMA transferred received frames from the MTL RX FIFO to memory in
minimal bursts and could not keep up with 1Gbit line rate: under a
sustained TCP RX stream the 4KB MTL RX FIFO overflows (the MTL RX
overflow counter increments) even while free RX descriptors are
available, capping TCP RX throughput at ~50Mbit/s on an OpenMV N6.  The
TX path already programs a burst length; set the RX side to 32 beats to
match the FIFO drain rate to line rate.

Verified on an OpenMV N6 (RGMII, 1Gbit link) with the MTL RX queue
overflow counter (MTLRXQ0MPOCR) and iperf-style Python benchmarks: this
change and the AXI outstanding-request limit change together stop the
FIFO overflows at line rate.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
The DMA system bus mode register's read and write outstanding-request
limit fields reset to zero (one outstanding AXI request), which
serialises the RX DMA's memory writes and, combined with the burst
length, determines how fast the MTL RX FIFO can drain.  At 1Gbit line
rate one outstanding request is not enough: the 4KB FIFO fills in a few
frame times whenever a burst arrives and the MTL drops frames (RX
overflow counter increments) even though free RX descriptors are
available.

Set both limits to their maximum of 4 outstanding requests.  Together
with programming the RX burst length this lets the RX DMA sustain line
rate on an OpenMV N6 (RGMII, 1Gbit link), verified with the MTL RX
overflow counter and Python socket benchmarks.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
The STM32N6 MAC is configured with checksum offload enabled (MACCR.IPC),
so the hardware verifies incoming IPv4 header and TCP/UDP/ICMP payload
checksums, and because the MTL RX queue operates in store-and-forward
mode with forward-error-packets left disabled, frames that fail those
checks are dropped before they reach the driver.  lwip was nevertheless
re-verifying every RX checksum in software, and because this driver runs
lwip's input path directly in the ETH IRQ handler, that work is done at
interrupt priority for every received frame.

Keep only the software ICMP checks (cheap and rare) and drop the
redundant IP/UDP/TCP ones on STM32N6.  Other MCUs are unchanged, as they
do not enable MACCR.IPC.

On an OpenMV N6 (RGMII, 1Gbit link) this raises sustained TCP RX
throughput measured from a Python socket benchmark by a further ~30%,
with TCP TX, UDP TX and UDP RX rates unchanged.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
kwagyeman moved this to In progress in OpenMV Features Aug 15, 2026
kwagyeman changed the title stm32/eth: Improve performance. stm32/eth: Improve N6 ethernet performance. Aug 15, 2026

github-actions Bot commented Aug 15, 2026
edited
Loading

Copy link
Copy Markdown

Code size report:

Reference:  lib/tinyusb: Update tinyusb submodule to MicroPython's fork. [791ba6e]
Comparison: stm32/mpu: Reset the MPU to a clean state before configuring it. [merge of 9e7ba11]
  mpy-cross:    +0 +0.000% 
   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:    +0 +0.000% standard
      stm32:    +0 +0.000% PYBV10
      esp32:    +0 +0.000% ESP32_GENERIC
     mimxrt:    +0 +0.000% TEENSY40
        rp2:    +0 +0.000% RPI_PICO_W
       samd:    +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:    +0 +0.000% VIRT_RV32

On ARMv8-M parts the bootloader can hand control to the application with
the MPU still enabled: the OpenMV N6 bootloader was observed to do so,
with CTRL=ENABLE|HFNMIENA|PRIVDEFENA and its own MAIR attributes still
in place.  mpu_init() then rewrote MAIR0, region 0 and finally CTRL
while the MPU was live and code was executing from external XSPI flash.
Reconfiguring a live MPU under XIP raises a spurious MemManage fault
(IACCVIOL) on the Cortex-M55 within a few instructions of the CTRL
write, but only when an instruction fetch happens to fall inside the
reconfiguration window -- so whether a given firmware boots depended on
its code layout.  Several OPENMV_N6 builds of current master fault on
every boot (parked in MemManage_Handler at reset, CFSR=IACCVIOL, before
reaching main), while other builds of the same source boot cleanly.

Fix by putting the MPU into a defined state first: disable it (with
DSB/ISB), clear every region, then configure MAIR0 and region 0 and
enable it.  With this change previously non-booting layouts boot
reliably (verified over repeated resets on an OpenMV N6), and layouts
that already worked are unaffected.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
kwagyeman changed the title stm32/eth: Improve N6 ethernet performance. stm32: Fix STM32N6 ethernet RX performance and MPU initialization. Aug 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

2 participants


Back | FazBrowse Home | New Git URL