Shop OBEX P1 Docs P2 Docs Learn Events
PNut/Spin2 Latest Version (v48.1 - preprocessor and flash-image saving) - Page 67 — Parallax Forums

PNut/Spin2 Latest Version (v48.1 - preprocessor and flash-image saving)

1646567697072

Comments

  • cgraceycgracey Posts: 14,232

    @Wuerfel_21 said:

    @cgracey said:
    Jeff and I were talking about this last night and thought it would best if the preprocessor variables came soley from above the object file, in the form of parameters. We were thinking that existing OBJ parameters could be seen by the preprocessor, and that would be it.

    someobject : "objectfile" | X = 1, Y = 2, Z = 3

    So, the preprocessor would only see X, Y, and Z. Someobject's internal CON blocks would also see X, Y, and Z, as is already the case. I think this would keep things clean, instead of chicken-and-egg conundrums with CON block declarations interacting with the preprocessor.

    My 50 cents (excl. tax):
    - every file should see definitions given on the command line
    - I don't think conflating CON symbols and defines is a good idea, so I'd use a different syntax for the latter. Maybe #X = 1.
    - flexspin has a #pragma exportdef X directive that makes a definition from the current file global for all files processed afterwards. This is a good idea, since that allows setting global defines without a command-line argument.

    I think you are right, Ada. That is how it will work.

  • Is not using this feature going to impact the speed of current programs? That is, can we expect a slow-down in Spin2 even if we're not using cooperative multitasking?

  • roglohrogloh Posts: 5,852
    edited 2024-12-05 23:21

    @Wuerfel_21 said:
    My 50 cents (excl. tax):
    - every file should see definitions given on the command line

    Yeah this is important if you want to be able to automate managing builds for things like feature combination testing where preprocessor inputs controlling the build could be varied in multiple ways. Otherwise you have to hand edit files or create different file combos for every build you do (or write lots of nasty scripts that do that part for you between builds) which quickly gets tedious with increasing numbers of combinations. Might be tolerable for a small infrequently built project, but not once you write larger code bases that need to be regularly build tested before release.

  • cgraceycgracey Posts: 14,232
    edited 2024-12-06 00:03

    @JonnyMac said:
    Is not using this feature going to impact the speed of current programs? That is, can we expect a slow-down in Spin2 even if we're not using cooperative multitasking?

    No extra code ever executes. Only the task-related instructions take any clock cycles.

  • evanhevanh Posts: 16,075

    That's an advantage co-operative has over pre-emptive. It only incudes overheads when explicitly used.

  • @cgracey Part of what I was going to attempt over the Christmas Holiday was to setup some type of cooperative multitasking in my web server. I may hold off a little bit, as what I had in mind would work perfectly with what you propose. That's a whole lot of book-keeping taken off my plate.

    Cooperative multitasking will allow me to have many TCP sockets open, without having to use multiple cogs that will be mostly idle.

    Thanks for adding this!

  • cgraceycgracey Posts: 14,232

    @ke4pjw said:
    @cgracey Part of what I was going to attempt over the Christmas Holiday was to setup some type of cooperative multitasking in my web server. I may hold off a little bit, as what I had in mind would work perfectly with what you propose. That's a whole lot of book-keeping taken off my plate.

    Cooperative multitasking will allow me to have many TCP sockets open, without having to use multiple cogs that will be mostly idle.

    Thanks for adding this!

    What do you think the maximum number of tasks might be that you would ever need at once?

  • @cgracey said:
    What do you think the maximum number of tasks might be that you would ever need at once?

    I only need 8 with my current hardware. I think the ESP32 can create 32 tasks. That might make for a good target.

  • evanhevanh Posts: 16,075

    @cgracey said:

    @evanh said:
    I'd want to be able to wait on a hardware event that then gives priority at task switch.

    EDIT: And as an extension - Waiting on any global changes. This wouldn't have any priority though as it would be using the task's own time to repeatedly resolve the equation being waited on.

    You could have hardware interrupts run PASM code which would set a flag for the interpreter to see when executing the branch instructions. That would be one way of making event-driven task switching.

    You might have to explain that. Would these flags be a built-in you'd be adding to the interpreter?

  • @ke4pjw said:

    @cgracey said:
    What do you think the maximum number of tasks might be that you would ever need at once?

    I only need 8 with my current hardware. I think the ESP32 can create 32 tasks. That might make for a good target.

    Or maybe 64 for the P2, as we have 64 pins, and spin in a 64 bit world!

  • cgraceycgracey Posts: 14,232
    edited 2024-12-06 10:20

    @ke4pjw said:

    @cgracey said:
    What do you think the maximum number of tasks might be that you would ever need at once?

    I only need 8 with my current hardware. I think the ESP32 can create 32 tasks. That might make for a good target.

    32 tasks is what I've settled on, because that many bits fit into a register. I think tasks need to have unchanging handles, once started, so that something like TASKID() can be used to alter task duty/sleep status, either by the task, itself, or by another task which has its handle.

    I've been working for many hours getting a quick stepping algorithm worked out, so that I can quickly determine what task is next, since the 32 task-active bits could wind up looking like Swiss cheese, as tasks start and stop, leaving holes behind that may not always be filled back in.

    It turns out that it is most code-efficient to assign task ID's from 31 to 0, not from 0 to 31. It comes down to the ENCOD instruction scanning from bit 31 towards bit 0. This actually has an added benefit, because I want to put the task pointers into register RAM for speed, and they can progressively occupy registers $11F down to $100, minimizing register usage.

    Here is a program I wrote to prove the task-stepping algorithm:

    taskcurr += encod (taskena ror taskcurr)

    It makes no sense to look at that line, because I started out with how you would think about it, got it working, then used algebraic substitution to make a more-efficient implementation.

    ' TASK stepping and free-task-selection
    
    pub start() | i, taskena, taskcurr, taskfree, r
    
      repeat 40 with i
    
        ifnot i // 20       'change task enables every 20th iteration
          taskena := getrnd()
          debug(ubin_long(taskena), "  Changed!")
    
        taskcurr += encod (taskena ror taskcurr)    'advance to the next task
        taskfree := encod !taskena                  'get the highest free-task number
    
        taskcurr &= $1F     'trim variables to 5 LSBs
        taskfree &= $1F
    
        debug(ubin_long(taskena), uhex_byte(taskcurr, taskfree))
    

    Here is the output that shows TASKCURR stepping through the active task bits in TASKENA:

    Here is the equivalent PASM code to step the current task:

            mov     x,taskena       'advance to the next task
            ror     x,taskcurr
            encod   x
            add     taskcurr,x
    

    Here is the PASM code to get the highest free-task number:

            not     x,taskena       'get the highest free-task number
            encod   x
    

    Small and fast.

    It's also important to assign new tasks in the order which they will be executed, so that if one task puts out data for the next task, it will be seen immediately, rather than having to come all the way around before seeing it.

  • @cgracey said:
    It turns out that it is most code-efficient to assign task ID's from 31 to 0, not from 0 to 31. It comes down to the ENCOD instruction scanning from bit 31 towards bit 0. This actually has an added benefit, because I want to put the task pointers into register RAM for speed, and they can progressively occupy registers $11F down to $100, minimizing register usage.

    I think people would prefer dealing with task numbers 0 to X instead of 31 to 31-X. Could you use the latter for task switching "behind-the-scenes" and convert where necessary?

  • cgraceycgracey Posts: 14,232
    edited 2024-12-06 18:32

    @TonyB_ said:

    @cgracey said:
    It turns out that it is most code-efficient to assign task ID's from 31 to 0, not from 0 to 31. It comes down to the ENCOD instruction scanning from bit 31 towards bit 0. This actually has an added benefit, because I want to put the task pointers into register RAM for speed, and they can progressively occupy registers $11F down to $100, minimizing register usage.

    I think people would prefer dealing with task numbers 0 to X instead of 31 to 31-X. Could you use the latter for task switching "behind-the-scenes" and convert where necessary?

    I agree with you. It would take a little more code, though.

    EDIT: It wound up taking a net of 2 more instructions to implement the 0..31 ordering from the user perspective.

  • You could efficiently allow for an arbitrary number of tasks, and allow people to implement their own custom schedulers, if you make TASKNEXT take a pointer (rather than an ID) to the next task to run, and make it so that passing 0 or -1 as the task pointer (or omitting the argument?) returns to the previous task or else to some default task, which would be ordinarily the scheduler. All normal task switches would then bounce through the scheduler. It then becomes the responsibility of the scheduler library to provide the memory for each task and to do all of the bookkeeping. This would allow us to have not only simple round-robin schedulers that only allows some fixed number of threads, but also more complex ones that know to only resume certain tasks when certain conditions are met.

  • cgraceycgracey Posts: 14,232
    edited 2024-12-06 18:50

    @Electrodude said:
    You could efficiently allow for an arbitrary number of tasks, and allow people to implement their own custom schedulers, if you make TASKNEXT take a pointer (rather than an ID) to the next task to run, and make it so that passing 0 or -1 as the task pointer (or omitting the argument?) returns to the previous task or else to some default task, which would be ordinarily the scheduler. All normal task switches would then bounce through the scheduler. It then becomes the responsibility of the scheduler library to provide the memory for each task and to do all of the bookkeeping. This would allow us to have not only simple round-robin schedulers that only allows some fixed number of threads, but also more complex ones that know to only resume certain tasks when certain conditions are met.

    As it is now, TASKNEXT() just steps to the next task. It doesn't take any parameter(s). I will think about the pointer possibility. It seems to me that the less tasks need to know about each other, the easier their lives are.

    I think I need to add a way to stall tasks, so that an asynchronous interrupt could just flip a bit to turn a task on or off, without altogether cancelling it or needing to start it. The round-robin mechanism could skip it that way, until it was activated again. I'd also need TASKHALT(task) and TASKCONT(task) instructions for within Spin2.

  • I would prefer that the multitasker be kept to the bare minimum in what it does. That simple level has proven perfectly adequate in forth over the years - no bells and whistles. Programmers are very ingenious, they will find many ways to achieve what they want on top of a simple multitasker and it won't bung the runtime up with little used code.

  • cgraceycgracey Posts: 14,232

    @bob_g4bby said:
    I would prefer that the multitasker be kept to the bare minimum in what it does. That simple level has proven perfectly adequate in forth over the years - no bells and whistles. Programmers are very ingenious, they will find many ways to achieve what they want on top of a simple multitasker and it won't bung the runtime up with little used code.

    I agree.

    I am kind of thinking there's no point to add variable duty control to the tasks. A single-bit halt/run control for each task could be really useful, though, so that they could be turned on and off without the restarting and stopping procedures. What do you think?

  • bob_g4bbybob_g4bby Posts: 440
    edited 2024-12-06 23:44

    "I am kind of thinking there's no point to add variable duty control to the tasks."

    That can also be done by the programmer at the start of the 'timed' task using SPIN2 (or PASM!). Read the system counter or do your favourite 'duty cycle' algorithm or whatever else determines the task should run. If the task doesn't meet the criteria to run, just execute TASKNEXT().

    Ideally, each task does something fairly simple (and short) to keep the multitasker spinning as quickly as possible. That keeps all the tasks nice and responsive. If something time consuming needs to be included, then maybe that's better done in another cog. All the task has to do is start that other cog and TASKNEXT(). Alternatively, no extra cog is needed if the time consumer is written as a state machine. TASKNEXT() is included in the "select next state" logic and if each state is kept quick, then the multitasking remains quick too.

    "A single-bit halt/run control for each task could be really useful, though"

    Kind of like a pause button for each task. If it's simple to do, put it in and see if people use it?

  • @cgracey said:

    @bob_g4bby said:
    I would prefer that the multitasker be kept to the bare minimum in what it does. That simple level has proven perfectly adequate in forth over the years - no bells and whistles. Programmers are very ingenious, they will find many ways to achieve what they want on top of a simple multitasker and it won't bung the runtime up with little used code.

    I agree.

    I am kind of thinking there's no point to add variable duty control to the tasks. A single-bit halt/run control for each task could be really useful, though, so that they could be turned on and off without the restarting and stopping procedures. What do you think?

    Variable duty could be emulated by putting all the rarely useful stuff into one task that would run something like

    repeat
      if checkA()
        handleA()
      tasknext()
      if checkB()
        handleB()
      tasknext()
      if checkC()
        handleC()
      tasknext()
    
  • cgraceycgracey Posts: 14,232
    edited 2024-12-07 10:27

    @Wuerfel_21 said:

    @cgracey said:

    @bob_g4bby said:
    I would prefer that the multitasker be kept to the bare minimum in what it does. That simple level has proven perfectly adequate in forth over the years - no bells and whistles. Programmers are very ingenious, they will find many ways to achieve what they want on top of a simple multitasker and it won't bung the runtime up with little used code.

    I agree.

    I am kind of thinking there's no point to add variable duty control to the tasks. A single-bit halt/run control for each task could be really useful, though, so that they could be turned on and off without the restarting and stopping procedures. What do you think?

    Variable duty could be emulated by putting all the rarely useful stuff into one task that would run something like

    repeat
      if checkA()
        handleA()
      tasknext()
      if checkB()
        handleB()
      tasknext()
      if checkC()
        handleC()
      tasknext()
    

    Yes, that would work.

    I figured that it would take enough time to do the duty-cycle computation within the round-robin sequencer, that it might be no less efficient to just run actual user code to make the decisions about executing more than just a check.

  • cgraceycgracey Posts: 14,232

    It's all working now, even with task halt/continue.

    I was able to arrange the interpreter's inter-instruction state registers into a contiguous block of 8, so that I can do Spin2 context switches very efficiently. Here is the task switcher, which deterministically goes to the next active unhalted task:

    '
    '
    ' TASKNEXT()
    '
    tasknexth       mov     w,pb                    'save current execution address in w
    
                    setq    #8-1                    'push current context (pbase/vbase/dbase/mrecv/msend/w/dcall/x)
                    wrlong  pbase,ptra++
    
                    altd    tasknum,#tasklist       'save current ptra in task pointer list
                    mov     0-0,ptra
    
                    mov     x,taskena               'step to next unhalted task
                    andn    x,taskhlt
                    ror     x,tasknum
                    encod   x
                    add     tasknum,x
                    and     tasknum,#$1F
    
                    alts    tasknum,#tasklist       'get next ptra from task pointer list
                    mov     ptra,0-0
    
                    setq    #8-1                    'pop next context (pbase/vbase/dbase/mrecv/msend/w/dcall/x)
                    rdlong  pbase,--ptra
    
            _ret_   rdfast  #0,w                    'restart bytecode execution in next task
    

    Here are all the new task commands:

  • cgraceycgracey Posts: 14,232
    edited 2024-12-07 12:09

    I just realized I could place the 'taskhlt' register right above the task-pointer list, at register $120. It wil stay there, going forward. This is so that PASM interrupt routines can be written which may periodically clear or set those HALT bits, or do so on events of interest, to trigger/pause Spin2 tasks.

    A Spin2 task can do TASKHALT(-1) to halt itself. Then, some cog-resident PASM interrupt routine can clear the task's HALT bit at $120 to get the task going again, after which the task can execute another TASKHALT(-1) to put itself back to sleep, until the PASM code wakes it up again.

    This is the first time that PASM code could gate Spin2 code execution. And it just takes writing to bits of interest in register $120.

  • cgraceycgracey Posts: 14,232

    I just posted a new v47 at the top of this thread which adds cooperative multitasking to Spin2.

    Here is a program I wrote to exercise multitasking. It starts up 16 tasks (1..16) that toggle pins 32..47 at different rates. While those tasks are running, the initial task (0) is doing TASKHALT and TASKCONT instructions randomly on tasks 1..16, and reporting their TASKCHK statuses, which are 1 for running and 2 for halted. Eventually, the 16 tasks run out of loops, then do a 'return', which causes the tasks to end, one by one, gradually freeing their ID's. This is observed by the primary task, which gets 0's back from the 16 tasks' statuses as they retire.

    {Spin2_v47}
    
    _clkfreq = 320_000_000
    
    var stk[100*16]
    
    pub start() | i
    
      repeat 16 with i
        taskspin(newtask, go(i), @stk[100*i])
        tasknext()
      repeat
        i := getrnd() +// 16 + 1
        taskhalt(i)
        debug(udec(i, taskchk(i)))
        wait_ms(200)
        taskcont(i)
        debug(udec(i, taskchk(i)))
        wait_ms(200)
    
    
    pri go(id)
    
      debug(udec(taskid(), taskchk(thistask)))
      repeat 160000
        pintoggle(32 + id)
        repeat 1 + id
          tasknext()
    
    pri wait_ms(x) | t
    
      t := getct() + clkfreq / 1000 * x
      repeat while getct() - t < 0
        tasknext()
    
  • bob_g4bbybob_g4bby Posts: 440
    edited 2024-12-07 13:32

    Just downloaded the zip file for v47. Something that didn't happen with v46 - Windows Security on my Win 10 machine reports:-
    (Sorry if this is a false alarm)

  • Here is a program I wrote to exercise multitasking.

    Now might be the time for a real-world demo. @"Ken Gracey" loves to build robots and he has platforms that could benefit from this.

    That said, can we have a multi-file (at least two) SD driver for the P2 that works with native Spin2 now? JonyJib needs it. I need it. Others would be happy to have it.

  • cgraceycgracey Posts: 14,232
    edited 2024-12-07 21:27

    @bob_g4bby said:

    @bob_g4bby said:
    Just downloaded the zip file for v47. Something that didn't happen with v46 - Windows Security on my Win 10 machine reports:-
    (Sorry if this is a false alarm)

    I asked Jeff about this and he gave me these instructions:

    Whenever that's happened, I send the file to Virus Total ( https://www.virustotal.com/gui/home/upload ) and screenshot the results. If only a few lesser-known-anti-virus systems show as "detected," it's actually clean (based on our historical results) and I don't worry about it and just release it and publish the SHA256 fingerprint also. Sometimes the more popular systems detect something and I've had to submit the file to them claiming I believe it's a false-positive (and then they confirm and update their system to eliminate the false positive.

    So, I will do this when I get back to my computer.

    Were you able to get around it?

  • I turned Windows Security off, downloaded the zip file and unpacked it all. Windows Security turns itself back on after a minute or so. Attempting to run PNUT_v47.exe aborts and the file is removed from the PNUT directory. I'm not familiar with this situation, so I've left it for now - it's a little late here in the UK.

  • cgraceycgracey Posts: 14,232

    @JonnyMac said:

    Here is a program I wrote to exercise multitasking.

    Now might be the time for a real-world demo. @"Ken Gracey" loves to build robots and he has platforms that could benefit from this.

    That said, can we have a multi-file (at least two) SD driver for the P2 that works with native Spin2 now? JonyJib needs it. I need it. Others would be happy to have it.

    I need it, too. I will be on it next.

  • evanhevanh Posts: 16,075

    @cgracey said:

    That said, can we have a multi-file (at least two) SD driver for the P2 that works with native Spin2 now? JonyJib needs it. I need it. Others would be happy to have it.

    I need it, too. I will be on it next.

    Another area, like the preprocessor, where following C's lead is desirable. Please use the same function names and arguments like fopen(), fread() and the likes. Porting C code will be a breeze then.

  • Another area, like the preprocessor, where following C's lead is desirable. Please use the same function names and arguments like fopen(), fread() and the likes. Porting C code will be a breeze then.

    +1

Sign In or Register to comment.