-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Add coproc
module
#6902
Add coproc
module
#6902
Conversation
Thanks, this looks neat! I didn't do any detailed review, but I do have some questions after a quick glance. How, if at all, does this control what pin(s) the coprocessor can access (read/write)? With rp2pio, which is conceptually in a similar vein, scott took care to make sure it could not interact badly with other pin uses like pwm or digitalio. However, it's also pretty easy to tell via introspection what it is possible for the loaded code to do. Saying that this is harder with risc-v code is an understatement to be sure. Is there example code (to load on the coprocessor) that could be used for testing? Is there an explanation of how to produce new code (assemble it)? Is there any sign this way of thinking would be useful on other microcontroller families? For instance, is there any way it could be used to access the 2nd CPU core on an rp2040? Is there any facility for the coprocessor and python to communicate after the coprocessor task has been launched, in either direction? Is there a reason there isn't anything along the lines of .stop(), .deinit(), or the functions to support usage in a |
The idea with this module is to provide pre-compiled examples on top of which a user can build there code.
The esp-idf provides toolchain for compiling risc-v co-processor code which is written in C or assembly.
I tested this with a simple blink example which is attached below. More examples are available in the esp-idf documentation.
I do expect to have this ability expand to other microcontroller families. I haven't looked into how this could be done on the rp2040 but have had discussions with discord community members in the past who requested this and say that this is possible.
For this, we can have api to access a shared global variable in case of the S2 for communication. I plan to add this in the future.
I just haven't looked down this path for the api. Any suggestions are welcome. 🚨 Simple Blink Example: # CircuitPython
import coproc
with open("ulp_blink.bin", "rb") as f:
coproc.load(f.read())
coproc.run()
while True:
pass #include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include "ulp_riscv/ulp_riscv.h"
#include "ulp_riscv/ulp_riscv_utils.h"
#include "ulp_riscv/ulp_riscv_gpio.h"
int main (void) {
bool gpio_level = true;
ulp_riscv_gpio_init(GPIO_NUM_21);
ulp_riscv_gpio_output_enable(GPIO_NUM_21);
while(1) {
ulp_riscv_gpio_output_level(GPIO_NUM_21, gpio_level);
ulp_riscv_delay_cycles(1000*1000 * ULP_RISCV_CYCLES_PER_US);
gpio_level = !gpio_level;
}
return 0;
} Here is the compiled version of the above ulp code. The zip contains Node: The python side has an infinite loop because when the execution ends all pins are reset due to which the blink program is no longer able to control the pin unless it initializes it again. I believe this is similar to your concern regarding pin access. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one thing about the args.
I am fine with including this and describing it as "experimental" and subject to change. @jepler do you have a comment?
I have made changes to the API and have added In the following example:
🚨 Blink Example: # CircuitPython
import coproc
shared_mem = coproc.CoprocMemory(address=0x500007fc, length=1024)
with open("program.bin", "rb") as f:
program = coproc.Coproc(buffer=f.read(), memory=shared_mem)
coproc.run(program)
print(coproc.memory(program)[0])
# coproc.halt(program)
while True:
pass // ULP-RISCV
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include "ulp_riscv/ulp_riscv.h"
#include "ulp_riscv/ulp_riscv_utils.h"
#include "ulp_riscv/ulp_riscv_gpio.h"
// global variables will be exported as public symbols, visible from main CPU
uint8_t shared_mem[1024];
uint16_t shared_mem_len = 1024;
int main (void) {
shared_mem[0] = 10;
shared_mem_len = 1024;
bool gpio_level = true;
ulp_riscv_gpio_init(GPIO_NUM_21);
ulp_riscv_gpio_output_enable(GPIO_NUM_21);
while(1) {
ulp_riscv_gpio_output_level(GPIO_NUM_21, gpio_level);
ulp_riscv_delay_cycles(shared_mem[0] * 10 * ULP_RISCV_CYCLES_PER_MS);
gpio_level = !gpio_level;
}
// ulp_riscv_shutdown() is called automatically when main exits
return 0;
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 can merge after builds
Very interesting addition. I'm trying to decide whether I'd find this useful for RP2040 or not: For the RP2040 though, it should already be possible to have native code run on the second core using native modules (.mpy); these are not explicitly documented in CircuitPython but the upstream docs contain examples. The native module approach has the added benefit that the module can expose a full python API for a very comfortable integration experience on the Python side, as well as being fully in control about starting, stopping, and communication with the other core (using the platform APIs, e.g. pico_multicore_fifo, without being limited by some architecture-independent abstractions. OTOH a benefit of this approach could be that it's not necessary to set up a full CircuitPython build environment, it's enough to have a third-party toolchain for the target architecture that can dump the binary machine code. EDIT: another benefit of this approach is that it can explicitly track the usage of the external core(s) and can e.g. raise an exception if the core is already running when a |
In a CircuitPython program, how do I determine a permissible address for a shared memory variable? What happens if I specify an address that is in use for some other purpose, such as one used by the CircuitPython or ESP-IDF heaps? Is this function safe to call with 'bad' inputs -- do they lead to exceptions, or do they continue and corrupt memory in use for another purpose? |
That can be grabbed from the build files
RTC_SLOW_MEM_START = 0x50000000
RTC_SLOW_MEM_END = RTC_SLOW_MEM_START + 8176
lower_limit = RTC_SLOW_MEM_START + loaded_program_len
upper_limit = RTC_SLOW_MEM_END - COPROC_MEMORY_LEN |
Hi! I think this is really neat but needs a bit more polish. Where should I put my feedback? Here or a new issue? |
An issue would be fine. |
Ok, I've opened #7218 |
The
coproc
module adds ability to load, run and halt pre-compiled programs on a co-processor or another cpu core.Currently, this is implemented on
esp32s2
andesp32s3
utilizing the built-inrisc-v
co-processor.