Skip to content

memorymap: Convert to use memoryview internally.#11110

Open
jepler wants to merge 1 commit into
adafruit:mainfrom
jepler:memorymap-memoryview
Open

memorymap: Convert to use memoryview internally.#11110
jepler wants to merge 1 commit into
adafruit:mainfrom
jepler:memorymap-memoryview

Conversation

@jepler

@jepler jepler commented Jul 10, 2026

Copy link
Copy Markdown

Importantly, a memoryview can be .cast(), and then accesses to it for 4 byte values don't require allocations or to/from_bytes. (as long as the register values actually fit in CircuitPython "small integers")

This does remove the existing protections enforcing aligned accesses to IO blocks on raspberrypi. However, the behavior in these cases is actually well defined: an 8 or 16 bit write access is replicated across all 32 bits of the register. See RP2040 datasheet 2.1.4. Narrow IO Register Writes and RP2350 datasheet 2.1.5. Narrow IO register writes.

As discussed on Discord, this speeds up the keyboard scanner of my Unicomp Mini M firmware by nearly 10x compared to using DigitalInOut, mostly because 12 pins can be read by a single operation, allowing it to scan the full keyboard in under 1ms when overclocked. (https://adafruit-playground.com/u/jepler/pages/unicomp-mini-m-with-circuitpython currently details the original lower performance keyboard, which took about 6ms to scan the full keyboard when overclocked)

Currently tested only on rp2040.

@jepler jepler force-pushed the memorymap-memoryview branch 2 times, most recently from 506fa9c to e8a23f3 Compare July 11, 2026 13:49
@jepler

jepler commented Jul 11, 2026

Copy link
Copy Markdown
Author

Here's the code I use in my program to read & update GPIO pins as a group:

SIO_BASE = const(0xd0000000)
SIO_LEN = const(0x1000)
GPIO_IN = const(4//4)
GPIO_OUT_XOR = const(0x1c//4)

sio = memorymap.AddressRange(start=SIO_BASE, length=SIO_LEN).cast('L')

def gpio_in():
    """Fetch the status of all GPIO inputs"""
    return sio[GPIO_IN]

def gpio_out_xor(x):
    """Invert the given pins simultaneously"""
    sio[GPIO_OUT_XOR] = x

@jepler jepler force-pushed the memorymap-memoryview branch 3 times, most recently from 3b0b1dd to db8b15f Compare July 11, 2026 16:11
@jepler

jepler commented Jul 11, 2026

Copy link
Copy Markdown
Author

Space Savings from this PR, raspberry_pi_pico:

  • 840b flash
  • 256b RAM
    The RAM savings could be had in main just by making the array of regions be "const", so that it resides in flash and is not copied to RAM.

@jepler jepler force-pushed the memorymap-memoryview branch from db8b15f to fd9766d Compare July 11, 2026 16:55
@jepler jepler requested a review from tannewt July 11, 2026 18:24
Importantly, a memoryview can be .cast(), and then accesses to it
for 4 byte values don't require allocations or to/from_bytes.

This does remove the existing protections enforcing aligned
accesses to IO blocks on raspberrypi. However, the behavior
in these cases is actually well defined: an 8 or 16 bit
access is replicated across all 32 bits of the register.
See RP2040 datasheet 2.1.4. Narrow IO Register Writes
and RP2350 datasheet 2.1.5. Narrow IO register writes.

Signed-off-by: Jeff Epler <jepler@unpythonic.net>
@jepler jepler force-pushed the memorymap-memoryview branch from fd9766d to e94f0f5 Compare July 11, 2026 18:30

@tannewt tannewt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall. One question about the future API. Could we make cast() check alignment?

//| the physical address space.
//| """
//|
//| def AddressRange(start_address: int, length: int) -> memoryview:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel like we'll want this long term. I'm happy to have this be in 10.x alongside a new api. Maybe we just want an access name for this? So you do memorymap.access(start, length) instead of memorymap.AddressRange(start, length). (AddressRange looks likes a class still. Thoughts?

@jepler

jepler commented Jul 13, 2026

Copy link
Copy Markdown
Author

Thanks for the review.

Looks good overall. One question about the future API. Could we make cast() check alignment?

As an alternative, what about an optional cast= or type= argument to the constructor (defaulting to 'B'), so that the desired type is returned immediately and alignment is checked then. Code can still get wrong alignment via a cast call but the obvious way to code it would avoid that.

We could change what the core memoryview.cast method does, but that would apply to any kind of object and might break existing code. There's not a way to change how cast works just for memoryviews that trace back to coming from this API, and the underlying memory layout (carefully shared among memoryview/bytes/bytearray/array.array) has no space I can see to store a "requires alignment" bit.

I don't feel like we'll want this long term. I'm happy to have this be in 10.x alongside a new api. Maybe we just want an access name for this?

I could add the name access and mark AddressRange as a depecated alias if you like.

@tannewt

tannewt commented Jul 13, 2026

Copy link
Copy Markdown
Member

Thanks for the review.

Looks good overall. One question about the future API. Could we make cast() check alignment?

As an alternative, what about an optional cast= or type= argument to the constructor (defaulting to 'B'), so that the desired type is returned immediately and alignment is checked then. Code can still get wrong alignment via a cast call but the obvious way to code it would avoid that.

Yup, that works for me. I think I'd prefer type but the comment can mention cast.

I don't feel like we'll want this long term. I'm happy to have this be in 10.x alongside a new api. Maybe we just want an access name for this?

I could add the name access and mark AddressRange as a depecated alias if you like.

Sounds good! Thanks for doing this!

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.

2 participants