Thanks to visit codestin.com
Credit goes to github.com

Skip to content

ports/rp2: Provide direct memory access to PIO FIFOs via proxy arrays. #7650

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 7, 2024

Conversation

nickovs
Copy link
Contributor

@nickovs nickovs commented Aug 12, 2021

As per the discussion in #7641, this PR adds a new get_fifo_proxy() method to rp2.StateMachine that allows a user to get a memory proxy for either the TX or RX FIFO of an instance. This can either be used directly to gain non-blocking access to the FIFO values or it can be passed to an rp2.DMA instance for the read or write values. For example:

my_sm = rp2.StateMachine(0, ...)
d = rp2.DMA()
a = bytearray(256)

c = d.default_ctrl
c.inc_read = False    # Fetch all the values from the same FIFO address
c.treq_sel = 8        # Flow control from the RX fifo for state machine 0

rx_fifo_proxy = my_sm.get_fifo_proxy(True)    # True for RX FIFO

d.config(read=rx_fifo_proxy, write=a, count=len(a)//4, ctrl=c, trigger=True)

STATIC mp_obj_t rp2_state_machine_get_fifo_proxy(mp_obj_t self_in, mp_obj_t read_not_write) {
rp2_state_machine_obj_t *self = MP_OBJ_TO_PTR(self_in);

mp_obj_array_t *proxy = m_new_obj(mp_obj_array_t);
Copy link
Member

Choose a reason for hiding this comment

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

Would this be better as a memoryview? Or because it's only 1 entry it doesn't really matter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made it an array since it never needs to be sliced, but since they are both mp_obj_array_t under the hood it doesn't make much difference.

Copy link
Member

Choose a reason for hiding this comment

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

If you print the resulting array object (eg at the REPL) then I think it will attempt to read the element, right? A memoryview would not. If reading from the FIFO modifies the FIFO then it might be better to use a memoryview.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wasn't aware of that difference, but it is a very good point. I will change it to be a memoryview.

Copy link
Member

Choose a reason for hiding this comment

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

>>> import array
>>> array.array('I', [123])
array('I', [123])
>>> array.array('B', b'1234')
array('B', [49, 50, 51, 52])
>>> memoryview(b'1234')
<memoryview>

@nickovs nickovs force-pushed the rp2_pio_fifo_proxy branch 2 times, most recently from 137e07b to 96fd89a Compare October 15, 2023 23:05
@nickovs
Copy link
Contributor Author

nickovs commented Oct 15, 2023

I have pushed a new version of this rebased against the current master, since it goes hand-in-hand with #7641, which is now tagged for release in 1.22.

@nickovs
Copy link
Contributor Author

nickovs commented Dec 28, 2023

@dpgeorge Now that the DMA support has been merged in, could you merge this too? I can then include documentation for this function in #7670 and make use of the function in the DMA examples.

@dpgeorge
Copy link
Member

Thinking about the API here: ideally we could use the buffer protocol (essentially __buffer__), and then simply pass a StateMachine instance directly to the DMA (which would use the mp_get_buffer() interface to retrieve the pointer). But the problem with this is that you need to distinguish between the read and write (tx/rx) addresses of the FIFO.

Maybe that can be done automatically, because when the DMA requests the buffer/pointer it does know whether it's for reading or writing, in the rp2_dma_register_value_from_obj() function. So if the buffer request is for MP_BUFFER_READ then the StateMachine returns the RX FIFO pointer. Otherwise if the request is for MP_BUFFER_WRITE then the StateMachine returns the TX FIFO pointer. This makes it very easy to write, eg:

d.config(read=my_sm, write=a, count=len(a)//4, ctrl=c, trigger=True)

You can still get a memoryview of the FIFO via:

memoryview(my_sm)

That will be for the RX FIFO. To access the TX FIFO... not sure exactly how to do that, but is that even needed?

Otherwise, the current get_fifo_proxy(read_not_write) method could be called fifo_proxy(), which is shorter and matches the names of the existing rx_fifo() and tx_fifo() methods. And for the argument, IMO it's a bit difficult to understand what True/False corresponds to without knowing up front. Constants like StateMachine.RX_FIFO and StateMachine.TX_FIFO would be better but that takes up a lot of code space. There could be separate methods like tx_fifo_proxy() and rx_fifo_proxy(), which matches the existing rx_fifo()andtx_fifo()even better. Or, instead, maybe new attributessm.tx_fifo_addrandsm.rx_fifo_addr` which return the address of the FIFO?

Still, I think I prefer the buffer protocol approach better.

@nickovs
Copy link
Contributor Author

nickovs commented Dec 29, 2023

Using the buffer protocol could make for tidy code, but it sort of makes me uneasy having a buffer protocol implementation that returns different values depending on if it's called for reading or writing.

I agree that having the read_not_write parameter is a little opaque, and the function name is a rather wordy. Combining some of the suggestions that you made above, how about I convert this into two attributes, sm.tx_proxy and sm.rx_proxy, that return the corresponding memoryview?

@dpgeorge
Copy link
Member

Combining some of the suggestions that you made above, how about I convert this into two attributes, sm.tx_proxy and sm.rx_proxy, that return the corresponding memoryview?

I think the API should match the existing patterns used by this class. That would be two separate methods then.

@nickovs
Copy link
Contributor Author

nickovs commented Dec 29, 2023

I think the API should match the existing patterns used by this class. That would be two separate methods then.

For clarity, you'd like sm.tx_proxy() and sm.rx_proxy()? If that's correct then I'll make it so and I will fix up the documentation accordingly.

@nickovs
Copy link
Contributor Author

nickovs commented Dec 29, 2023

So, upon reflection (and reading the RP2040 datasheet), I think that the buffer protocol idea might be best, but if we go that way then I think we should also implement the same thing for the UART and SPI objects. Both of those peripherals use the same address for reading and writing, so the implementation is simpler for them. Note that for this to work we need to fix the DMA class's code in the that gets the buffer address, since at the moment it only ever asks for a readable buffer.

If you agree then I will fix up this PR, including the fix needed for the issue mentioned above, and I will take a stab at a separate PR to add buffer protocol support for the SPI and UART objects.

@nickovs nickovs force-pushed the rp2_pio_fifo_proxy branch 2 times, most recently from 45a683e to dfd2fa5 Compare December 31, 2023 18:30
Copy link

Code size report:

   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:    +0 +0.000% standard
      stm32:    +0 +0.000% PYBV10
     mimxrt:    +0 +0.000% TEENSY40
        rp2:   +64 +0.019% RPI_PICO
       samd:    +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS

@nickovs
Copy link
Contributor Author

nickovs commented Dec 31, 2023

I have updated this to use the buffer protocol, and also added buffer protocol support for the SPI data FIFO.

Copy link

codecov bot commented Dec 31, 2023

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (80fd575) 98.36% compared to head (45a683e) 98.40%.

❗ Current head 45a683e differs from pull request most recent head 1da45e8. Consider uploading reports for the commit 1da45e8 to get more accurate results

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #7650      +/-   ##
==========================================
+ Coverage   98.36%   98.40%   +0.04%     
==========================================
  Files         159      159              
  Lines       21088    21088              
==========================================
+ Hits        20743    20752       +9     
+ Misses        345      336       -9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@dpgeorge
Copy link
Member

dpgeorge commented Jan 2, 2024

This looks good now, using the buffer protocol on PIO and SPI.

So, can you now pass a spi instance to the DMA .config() method and have it read/write the SPI from DMA?

Is it also possible to make this work with UART? (At least in principle, so we don't have to have a special case for UART.)

The only downside I can imagine for using the buffer protocol this way (for the address to pass to DMA) is that maybe in the future there needs to be another kind of buffer access on SPI/PIO/UART/etc. Eg for UART maybe you want to access the underlying ring-buffer, rather than the hardware data register. But that seems rather unlikely. I think using the buffer protocol to interface with DMA is a good idea.

(An alternative would be to make the rp2.DMA object aware of SPI/PIO/UART/etc and if passed one of these objects then the DMA would know how to get the correct register address for the given object. That would simply be a big switch on the incoming object type. Using the buffer protocol is much more elegant.)

Can you please split this up into two separate commits: one to fix the DMA code that gets the buffer address (that's definitely a bug!), and another commit to implement the buffer interface on PIO and SPI?

@nickovs
Copy link
Contributor Author

nickovs commented Jan 2, 2024

So, can you now pass a spi instance to the DMA .config() method and have it read/write the SPI from DMA?

Yes, an SPI object can be passed directly to either the read or write argument in .config(), just like a state machine can.

I can also do this for UART object. The only slight complexity there is that the UART class gets defined in extmod/machine_uart.c rather than on the port, so I will need to define MICROPY_PY_MACHINE_UART_BUFFER or something similar so that I can conditionally add the buffer protocol entry in the class definition.

Regarding your speculation that we might want to use the buffer interface on some device for some other purpose, I guess it might happen one day, but I struggle to see what that use case would look like. Most peripherals only have two types of register: configuration and data transfer. It's possible that one day someone will come up with a reason to use a buffer to access some other part of the hardware but the current proposal seems to fit for all the use cases we have in hand. If there is another, more unusual use case then a function could be provided to fetch a proxy memoryview. The big advantage of going this route rather than having a switch statement inside the DMA object is that it provides much cleaner separation; if future chips have more or different peripherals then we only need to make updates to the driver for the specific device.

I'm happy to split the bug fix. Do you want that as a separate commit in the single PR, or as a separate bug fix PR?

@dpgeorge
Copy link
Member

dpgeorge commented Jan 3, 2024

I'm happy to split the bug fix. Do you want that as a separate commit in the single PR, or as a separate bug fix PR?

Just a separate commit in this PR would be fine, thanks.

(One of the reasons for splitting out the bug fix is so it can be potentially merged to a patch-release branch.)

@nickovs nickovs force-pushed the rp2_pio_fifo_proxy branch from 5f49069 to 4dd26fe Compare January 6, 2024 23:34
@nickovs
Copy link
Contributor Author

nickovs commented Jan 6, 2024

@dpgeorge This is now split into two commits: one for the fix and one for the new functionality.

@dpgeorge dpgeorge force-pushed the rp2_pio_fifo_proxy branch from 4dd26fe to 1da45e8 Compare January 7, 2024 07:27
@dpgeorge dpgeorge merged commit 1da45e8 into micropython:master Jan 7, 2024
@dpgeorge
Copy link
Member

dpgeorge commented Jan 7, 2024

Thanks for updating. Now merged.

@nickovs nickovs deleted the rp2_pio_fifo_proxy branch January 7, 2024 20:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants