• Multicore Forth execution on the RP2040 (cont'd)

    02/05/2022 at 02:51 0 comments

    Recent versions of zeptoforth (release 0.25.0 or newer) now support straightforward symmetric multiprocessing in that they support interaction between tasks executing on different cores of the RP2040 interacting with one another with task communication and synchronization constructs, which was not supported by older versions of zeptoforth which only supported multicore execution where different cores did not interact with each other aside from through shared RAM, hardware spinlocks, and hardware FIFO's. Additionally any task on any core can now start a task on any given core, where previously one had to explicitly boot the second core of the RP2040 from the first core and aside from that tasks could only spawn tasks on their own core.

    For an example of multicore operation combined with multitasking with shared task communication and synchronization constructs, take the following example:

    continue-module forth
    
      task import
      systick import
      chan import
    
      \ The data size
      1 constant element-size
      
      \ The intermediate stream byte count
      256 constant inter-count
    
      \ The endpoint stream byte count
      256 constant end-count
    
      \ The endpoint counter interval
      1000 constant end-interval
    
      \ The tasks
      variable producer-task
      variable inter-task
      variable consumer-task
    
      \ The streams
      element-size inter-count chan-size buffer: inter-chan
      element-size end-count chan-size buffer: end-chan
    
      \ The data buffers
      element-size buffer: source-send-buf
      element-size buffer: inter-recv-buf
      element-size buffer: end-recv-buf
    
      \ The receive count
      variable recv-count
    
      \ The starting systick
      variable start-systick
    
      \ Our producer
      : producer ( -- )
        source-send-buf element-size 0 fill
        begin source-send-buf element-size inter-chan send-chan again
      ;
    
      \ Our intermediate
      : inter ( -- )
        begin
          inter-recv-buf element-size inter-chan recv-chan
          inter-recv-buf swap end-chan send-chan
        again
      ;
    
      \ Our consumer
      : consumer ( -- )
        begin
          1 recv-count +!
          end-recv-buf element-size end-chan recv-chan drop
          recv-count @ end-interval > if
            0 recv-count !
            systick-counter dup start-systick @ -
            cr ." Receives per second: " 0 swap 0 end-interval f/
            10000,0 f/ 1,0 2swap f/ f.
            start-systick !
          then
        again
      ;
    
      \ Initialize our test
      : init-test ( -- )
        0 recv-count !
        systick-counter start-systick !
        element-size inter-count inter-chan init-chan
        element-size end-count end-chan init-chan
        0 ['] consumer 320 128 512 0 spawn-on-core consumer-task !
        0 ['] inter 320 128 512 1 spawn-on-core inter-task !
        0 ['] producer 320 128 512 0 spawn-on-core producer-task !
        consumer-task @ run
        inter-task @ run
        producer-task @ run
      ;
      
    end-module

    In this example, three tasks are spawned, a consumer task on core 0, an intermediate task on core 1, and a producer task on core 0. Two queue channels (a task synchronization and communication construct that consists of a queue which one or more tasks send messages to and which one or more task receive messages from) are created, one for communication from the producer task to the intermediate task, and one for communication from the intermediate task to the consumer task.

    The producer task continually sends messages to the intermediate task with the only delays being when the channel for communication from the producer task to the intermediate task is full. The intermediate task continually receives single messages from the producer task and sends the same messages to the consumer task. The consumer task continually receives messages from the intermediate task, and times how long it takes for a given number of messages to be received, with which it calculates how many messages it has received per second and displays it to the user..

    By this inter-task communication is demonstrated to function across multiple cores using task communication and synchronization constructs. Likewise, one task can use RUN to initiate a task on another core, as also shown here. (Unlike previously, spawning a task on another core no longer...

    Read more »

  • Multicore Forth execution with multitasking on the RP2040

    11/23/2021 at 03:10 0 comments

    RP2040 (e.g. Raspberry Pi Pico) support in zeptoforth includes symmetric multiprocessing support, such that multicore operation is combined with multitasking, aside from that the RP2040 must be rebooted, programmed, or erased from core 0, and the main task running the REPL is always on core 0. Tasks can be spawned on arbitrary cores, and tasks on different cores can use multitasking constructs to communicate with one another.

    Here is the source of a test program for demonstrating multicore execution combined with multitasking on the RP2040, repeated here as well:

    continue-module forth
    
      task import
      led import
    
      \ LED test loop task
      : led-loop ( ms -- ) begin led-toggle dup ms again ;
    
      \ Star test loop task
      : star-loop ( ms -- ) begin ." *" dup ms again ;
    
      \ Second core main task
      : core-1-main ( star-ms led-ms -- )
        1 ['] led-loop 256 128 512 spawn run
        1 ['] star-loop 256 128 512 spawn run
        current-task kill
      ;
    
      \ Initialize the test
      : init-test ( -- )
        1000 500 2 ['] core-1-main 256 128 512 1 spawn-on-core run
        250 ms 750 1 ['] led-loop 256 128 512 spawn run
      ;
      
    end-module

    What this code does is, once compiled to RAM and init-test is invoked, first it spawns a task on core 1 with spawn-on-core with the specified dictionary, data stack, and return stack sizes in bytes, with an execution token for core-1-main, which will be executed as the main task of core 1, and two arguments, 1000 and 500, which will be pushed onto the data stack of the main task of core 1 as well as their count.

    The main task of core 1 then spawns two tasks on core 1 which each take 1 argument off its data stack and have the specified stack configuration, which execute led-loop and star-loop, and then terminates itself. led-loop takes a number of milliseconds and toggles the LED at that interval forever, and star-loop does the same except it outputs asterisks to serial IO.

    Back in the main task of core 0 from which init-test was invoked, it waits 250 ms and then spawns a task on core 0 which executes led-loop with an argument of 750, to toggle the same LED toggled by led-loop on core 1 but at an interval of 750 ms. After this control is returned to the REPL on core 0.