Minimal cooperative task-switcher for PSOC?

A catchall for PSoC Mixed-Signal Array (microcontroller) discussions not captured by the other forums.

Moderator: ericb

Minimal cooperative task-switcher for PSOC?

Postby supercat » Tue May 04, 2004 1:29 pm

One approach I've found very handy for task-switching on micros like the 8x51 is to have a taskswap() routine equivalent to the following:
Code: Select all
_taskswap:
  mov a,[_altsp]
  swap a,sp
  mov [_altsp],a
  ret


To use this, one would set up the alternate task via something like:
Code: Select all
char altsp;
char altstack[32];

{
...
  altstack[0] = ((unsigned short)other_proc) >> 8;
  altstack[1] = ((unsigned short)other_proc) & 255;
  altsp = ((unsigned short)altstack) & 255;
...
}


Once this is done, user code can run until it's waiting for something, whereupon it can call taskswap(). The function other_proc() will then run until it calls taskswap() whereupon the main program will resume where it left off.

I've found this sort of artchitecture very handy on the 8x51 when writing code with two main tasks, both of which spent a lot of time "waiting" for things. I'd think such a thing should work well on the PSOC, but am curious whether anyone has done it. My biggest concern would be whether it would be necessary to save/restore the compiler registers, or if such registers never hold anything meaningful at sequence points. If saving/restoring registers is necessary, then task switching becomes more complicated. If not, though, it's often easier to task-switch whether or not it's needed than to decide when to do so. For example:
Code: Select all
char getch(void)
{
  while (!char_ready())
    taskswap();
  return input_char();
}

Even if there's no character ready, and even if the other task had something useful it could do when it called taskswap(), the overhead for switching tasks back and forth is probably less than the overhead that would be required for a more 'advanced' OS to decide that no task swap was needed.

Anyone else done anything similar on the PSOC? Does it look like a useful concept?
supercat
Cheese Wheel
Cheese Wheel
 
Posts: 117
Joined: Thu Feb 19, 2004 6:14 pm

Postby jmmilner » Wed May 05, 2004 9:55 am

The only issue I can see with the current PSoC C compiler is that it uses the X register as the stack frame pointer. To switch from one thread to another, both written in C as your example suggests, I'd try:
Code: Select all
_taskswap:
  push x
  mov a,[_altsp]
  swap a,sp
  mov [_altsp],a
  pop x
  ret

To answer your concern about the compiler pseudo-registers, they are only used within a single statement. As long as you declare taskswap() as type void and use a simple unadorned function call to taskswap(), there should be no problem. However, if you start to get tricky with passing arguments to or returning a result from taskswap() you could run into trouble. Even if the compiler was improved to do optimization across statement boundaries (it barely does optimization within a statement at present), the semantics of C should still make this safe if taskswap() is declared as extern void.

With the limited total RAM of the current PSoCs (256 bytes), I'd guess this concept would require careful design so as not to exhaust memory even with the one added stack. Once the 2K RAM parts become available, I see a great deal more use for lightweight threads and minimalist context switching.
jmmilner
Cheese Wheel
Cheese Wheel
 
Posts: 106
Joined: Sun Feb 22, 2004 8:37 am
Location: edge of a corn field

Postby supercat » Wed May 05, 2004 9:58 pm

jmmilner wrote:The only issue I can see with the current PSoC C compiler is that it uses the X register as the stack frame pointer.


I thought that A and X were explicitly required to be caller-preserved if the caller needed their values. Certainly many of the existing library functions trash both A and X.

With the limited total RAM of the current PSoCs (256 bytes), I'd guess this concept would require careful design so as not to exhaust memory even with the one added stack. Once the 2K RAM parts become available, I see a great deal more use for lightweight threads and minimalist context switching.


That probably depends on the extent to which one can manage stack usage by interrupts. Creating a separate stack for interrupts can mitigate such problems, but introduces its own wrinkles.
supercat
Cheese Wheel
Cheese Wheel
 
Posts: 117
Joined: Thu Feb 19, 2004 6:14 pm

Postby jmmilner » Thu May 06, 2004 8:59 am

I thought that A and X were explicitly required to be caller-preserved if the caller needed their values. Certainly many of the existing library functions trash both A and X.
I believe you're referring to assembly language functions which use the fastcall convention (#pragma fastcall function_name). If you added #pragma fastcall taskswap, the compiler would generate the necessary push X and pop X surrounding each call to taskswap. Since one of the points of writing functions is to perform repeated actions in one place, I'd do the save/restore of X once in taskswap rather than have every instance of a call to taskswap do it.

In the current compiler the division of labor between the caller and the called function (both in C) is:
Code: Select all
Caller:
  push arguments on stack
  lcall function
Callie:
  push X ; save callers stack frame pointer
  mov X,SP ; establish our own stack frame
  add SP,n ; (optional) allocate space for locals
  ....
  add SP,-n ; (optional) free stack space used by locals
  pop X ; restore callers stack frame pointer
  ret
Caller:
  {code to handle return value, if any}
  add SP,-m ; pop calling arguments off stack

Functions written in C can't use the fastcall convention (Section 5.2, page 34, PSoC Designer: C Language Compiler Usr Guide).
jmmilner
Cheese Wheel
Cheese Wheel
 
Posts: 106
Joined: Sun Feb 22, 2004 8:37 am
Location: edge of a corn field

Postby supercat » Thu May 06, 2004 7:37 pm

jmmilner wrote:
I thought that A and X were explicitly required to be caller-preserved if the caller needed their values. Certainly many of the existing library functions trash both A and X.
I believe you're referring to assembly language functions which use the fastcall convention (#pragma fastcall function_name). If you added #pragma fastcall taskswap, the compiler would generate the necessary push X and pop X surrounding each call to taskswap. Since one of the points of writing functions is to perform repeated actions in one place, I'd do the save/restore of X once in taskswap rather than have every instance of a call to taskswap do it.


Okay, that certainly makes sense. Otherwise, it would seem the most significant problem would be with interrupts. In a single-tasking system, it's no less efficient for an interrupt to use stack space than fixed addresses, but in a multi-tasking system that's no longer true. Is it practical to keep interrupt stack usage down to a few bytes, or do some "component libraries" need more than that?

The 8x51 compiler for which I wrote my mini-tasker used fixed addresses for auto variables, and thus interrupt stack usage was only about 3 bytes worst-case. But if an interrupt needs 16 bytes, that would require allocating 16 extra bytes on both stacks which could be expensive.

Alternatively, one could use an separate stack which is used only by the interrupt handlers, or one could modify the interrupt entry/exit code so as to always use one particular stack. Perhaps something like [I don't have my asm guide handy]
Code: Select all
interrupt_handler:
  push a
  mov a,_intstack
  swap sp,a
  push a
  push x
  ... handle interrupt
  pop x
  pop a
  swap sp,a
  pop a
  reti


Does that look reasonable?
supercat
Cheese Wheel
Cheese Wheel
 
Posts: 117
Joined: Thu Feb 19, 2004 6:14 pm

Postby jmmilner » Thu May 06, 2004 10:31 pm

Stack usage in assembly language ISRs can be very modest if that is a design goal. If you want to support C ISRs, you can't call any functions inside the ISR unless you are willing to pay the price of having all the compiler virtual registers pushed on the stack (up to 15 bytes). Even if you don't call other functions, the compiler must still save the virtual registers it uses in the generated code for the ISR function itself (typically 4 to 7 bytes).
jmmilner
Cheese Wheel
Cheese Wheel
 
Posts: 106
Joined: Sun Feb 22, 2004 8:37 am
Location: edge of a corn field


Return to “%s” PSoC1 General

Who is online

Users browsing this forum: No registered users and 0 guests

cron