So that means the delays are really in clock cycles and not in opcodes ?
An immediate WAITxx opcode, will have just a brief pause, until normal threads resume ?
As I understand it, as soon as TFREE is called, multitasking is re-enabled. The trailing instructions are executed in multitask mode. So, WAITxxx would behave however it normally behaves in multi-task mode. If they still do the self-jump (to avoid a pipeline stall), then that's what they would do.
As I understand it, as soon as TFREE is called, multitasking is re-enabled. The trailing instructions are executed in multitask mode. So, WAITxxx would behave however it normally behaves in multi-task mode. If they still do the self-jump (to avoid a pipeline stall), then that's what they would do.
So the other tasks do resume ok, while this one waits ?
ie It takes 2 clocks to flip-back tasks, and with no stall, other tasks fetch their opcodes ok.
(and the waitxx will become more granular, once tasking resumes)
So the other tasks do resume ok, while this one waits ?
ie It takes 2 clocks to flip-back tasks, and with no stall, other tasks fetch their opcodes ok.
(and the waitxx will become more granular, once tasking resumes)
The other tasks resume, while a WAITxxx trailing TFREE self-loops.
You brought up a good point: Are multitasking WAITxxx self-looping rules in effect during TLOCK? Yes, but I think they shouldn't be.
I just made it so that when a task is locked, WAITxxx instructions stall the pipeline, instead of loop to themselves, as if multitasking was 'off'. This will make it possible to do cycle-accurate waits within locked tasks, and it will speed things up.
Here is the updated scheme, with 'wait' instructions behaving differently within locked tasks:
NOTP #0 'these instructions execute according to the time slots
NOTP #0 '('wait' instructions will loop to themselves)
TLOCK 'execute only this task, beginning at the next instruction
NOTP #0 'these instructions execute at full-speed
NOTP #0 '('wait' instructions will stall the pipeline)
NOTP #0
TFREE 'resume multitasking after two more instructions
NOTP #0 'these two instructions still execute at full-speed
NOTP #0 '('wait' instructions will loop to themselves)
NOTP #0 'these instructions now execute according to the time slots
NOTP #0 '('wait' instructions will loop to themselves)
I just made it so that when a task is locked, WAITxxx instructions stall the pipeline, instead of loop to themselves, as if multitasking was 'off'. This will make it possible to do cycle-accurate waits within locked tasks, and it will speed things up.
Sounds good, but I would have expected a 100% task, to be cycle accurate on WAITxx without needing fixes ?
Was something else more subtle going on - was loop-to-self not a 1 cycle operation when done in a 100% task ?
Sounds good, but I would have expected a 100% task, to be cycle accurate on WAITxx without needing fixes ?
Was something else more subtle going on - was loop-to-self not a 1 cycle operation when done in a 100% task ?
A loop-to-self takes 4 clocks in single-tasking because three lower pipeline stages must be flushed. In 4-way tasking, nothing needs flushing, so those loops-to-self only take 1 clock, effectively. Stalling the pipeline until the event occurs is way more efficient for single-tasking (as you know).
A loop-to-self takes 4 clocks in single-tasking because three lower pipeline stages must be flushed. In 4-way tasking, nothing needs flushing, so those loops-to-self only take 1 clock, effectively. Stalling the pipeline until the event occurs is way more efficient for single-tasking (as you know).
Ahh, then certainly a good fix, and it ensures no-hidden-changes when moving from single to multi tasking.
Does this mean that a single task, when not locked, has the 4 cycle Wait-loops-to-self, but if you wanted the better granularity on a single-task, you could use the 100% slices command, (never restored), to flip modes on WAITxx ?
Ahh, then certainly a good fix, and it ensures no-hidden-changes when moving from single to multi tasking.
Does this mean that a single task, when not locked, has the 4 cycle Wait-loops-to-self, but if you wanted the better granularity on a single-task, you could use the 100% slices command, (never restored), to flip modes on WAITxx ?
If SETTASK established multi-tasking (more than one task in the loop), then all tasks' 'waits' will loop to themselves, to accommodate the other tasks. The only time the pipeline is stalled for 'waits' is when you have only one task in the task loop, or you've locked a task temporarily.
Just a thought. It seems to me that the addition of TLOCK/TFREE has introduced a single-bit flag for whether in single or multi-task mode. If TLOCK/TFREE were instead STASK/MTASK (for example) to switch between single and multi-task modes, then it would be possible to call SETTASK without triggering multi-task mode. To be clear, MTASK would be the only way to enable tasking and STASK would be the only way to disable tasking, while SETTASK only sets the task configuration register.
WOW - I am impressed. With all the help given to Chip with this, what a difference a day makes. I am really thrilled that Chip lets us share the joy in the design, as well as input into it.
While reading this thread, I thought of a possible naming solution. Then I read Seairth's thread and he is almost onto what I thought. How about...
Just a thought. It seems to me that the addition of TLOCK/TFREE has introduced a single-bit flag for whether in single or multi-task mode. If TLOCK/TFREE were instead STASK/MTASK (for example) to switch between single and multi-task modes, then it would be possible to call SETTASK without triggering multi-task mode. To be clear, MTASK would be the only way to enable tasking and STASK would be the only way to disable tasking, while SETTASK only sets the task configuration register.
I disagree, SETTASK is fine, it's an atomic operation and it eliminates the need for *2* instructions.
TaskLOCK and TaskFREE are fine, and I can see them used quite extensively.
The problem with using STASK/MTASK is that then you force the (new) programmers to learn yet more hoops to jump through for making their code bug free. With just SETTASK, it's only a single instruction for adjusting the registers, which the chip then acts on.
Just a thought. It seems to me that the addition of TLOCK/TFREE has introduced a single-bit flag for whether in single or multi-task mode. If TLOCK/TFREE were instead STASK/MTASK (for example) to switch between single and multi-task modes, then it would be possible to call SETTASK without triggering multi-task mode. To be clear, MTASK would be the only way to enable tasking and STASK would be the only way to disable tasking, while SETTASK only sets the task configuration register.
Interesting idea. Right now, if you are multitasking and do TLOCK, you can do SETTASK without it taking effect until after TFREE. What you are proposing would be reversing the perspective a little bit, which I think makes more sense - multitasking ON or OFF, rather than thread LOCKed or FREEd. The one bugger is that you would have to do two instructions (SETTASK+MTASK) to start multitasking. I think the way we have it now is most efficient. We just need some good mnemonics.
Most of the time you will be using the SETTASK without the TLOCK/TFREE instructions, so you've just caused evey multitasking program (if it is running in COG memory) to eat up a precious LONG for an instruction that really doesn't gain anything for anyone.
I guess I don't see the benefit of being able to set the task mask without activating it and then having an instruction to activate it/deactivate it other than using up a COG memory register every time you want to multi-task.
Interesting idea. Right now, if you are multitasking and do TLOCK, you can do SETTASK without it taking effect until after TFREE. What you are proposing would be reversing the perspective a little bit, which I think makes more sense - multitasking ON or OFF, rather than thread LOCKed or FREEd. The one bugger is that you would have to do two instructions (SETTASK+MTASK) to start multitasking. I think the way we have it now is most efficient. We just need some good mnemonics.
current paradigm: TASKHLD/TASKREL
your paradigm: MULTASK/ONETASK
It is true that this would require two instructions to start multitasking. For the vast majority of applications, this likely means adding one instruction to the entire codebase. This hardly seems like a burden.
As for pedward's comment about forcing new programmers to learn more hoops, I think my approach is simpler to learn.
The current rule set:
SETTASK sets the task register. If there is not an active TASKHLD, then it also starts multitasking. If there is an active TASKHLD, the new task register takes affect when TASKREL is called.
TASKHLD temporarily halts multitasking.
What happens if TASKHLD is called before the first call to SETTASK?
If in single task mode and the sequence of TASKHLD->SETTASK->TASKREL is executed, does multitasking start after TASKREL?
The only way to actually stop multitasking is to use SETTASK and set all slots to the same value (presumably with the task ID of the task that's actually calling SETTASK).
The proposed rule set:
SETTASK sets the task register. If multitasking is enabled, the new task register takes affect immediately.
MULTASK enables multitasking. Even if the task register is all set to a single task, multitasking is enabled.
ONETASK disables multitasking. The task that calls ONETASK is always the task that will remain running after the call.
If multitasking is re-entered without calling SETTASK, the tasking picks up where it left off when ONETASK was called.
Which set of rules would be easier for programmers to learn?
Also, as I mentioned earlier, the current code for detecting whether in multi or single task mode has got to involve reading the task register and doing some sort of two-bit reduction/test to see if all fields have the same value. And then there's an addition one-bit test to see whether multitasking is paused. Even if the reduction/test is done once curing SETTASK and stored in a one-bit register, you still have a two-bit value that must be evaluated to handle multitasking mode. With the proposed approach, there is only the one-bit flag. The value of the task register has no affect on whether multitasking is enabled or not. This might not make much difference for gate count, but it might help critical path timing.
It is true that this would require two instructions to start multitasking. For the vast majority of applications, this likely means adding one instruction to the entire codebase. This hardly seems like a burden.
As for pedward's comment about forcing new programmers to learn more hoops, I think my approach is simpler to learn.
The current rule set:
SETTASK sets the task register. If there is not an active TASKHLD, then it also starts multitasking. If there is an active TASKHLD, the new task register takes affect when TASKREL is called.
TASKHLD temporarily halts multitasking.
What happens if TASKHLD is called before the first call to SETTASK?
If in single task mode and the sequence of TASKHLD->SETTASK->TASKREL is executed, does multitasking start after TASKREL?
The only way to actually stop multitasking is to use SETTASK and set all slots to the same value (presumably with the task ID of the task that's actually calling SETTASK).
The proposed rule set:
SETTASK sets the task register. If multitasking is enabled, the new task register takes affect immediately.
MULTASK enables multitasking. Even if the task register is all set to a single task, multitasking is enabled.
ONETASK disables multitasking. The task that calls ONETASK is always the task that will remain running after the call.
If multitasking is re-entered without calling SETTASK, the tasking picks up where it left off when ONETASK was called.
Which set of rules would be easier for programmers to learn?
Also, as I mentioned earlier, the current code for detecting whether in multi or single task mode has got to involve reading the task register and doing some sort of two-bit reduction/test to see if all fields have the same value. And then there's an addition one-bit test to see whether multitasking is paused. Even if the reduction/test is done once curing SETTASK and stored in a one-bit register, you still have a two-bit value that must be evaluated to handle multitasking mode. With the proposed approach, there is only the one-bit flag. The value of the task register has no affect on whether multitasking is enabled or not. This might not make much difference for gate count, but it might help critical path timing.
I think this just might boil down to semantics and that, currently, MULTASK is the default. In your case, ONETASK would be the default. We could use ONETASK/MULTASK in lieu of TASKHLD/TASKREL and be in the same place, never minding the default mode.
As far as finding out whether or not you are in multitasking, does it really matter, since you might find you are or aren't but on the next instruction the reality has changed?
SETTASK sets the task register. If there is not an active TASKHLD, then it also starts multitasking. If there is an active TASKHLD, the new task register takes affect when TASKREL is called.
TASKHLD temporarily halts multitasking.
What happens if TASKHLD is called before the first call to SETTASK?
If in single task mode and the sequence of TASKHLD->SETTASK->TASKREL is executed, does multitasking start after TASKREL?
The only way to actually stop multitasking is to use SETTASK and set all slots to the same value (presumably with the task ID of the task that's actually calling SETTASK).
The proposed rule set:
SETTASK sets the task register. If multitasking is enabled, the new task register takes affect immediately.
MULTASK enables multitasking. Even if the task register is all set to a single task, multitasking is enabled.
ONETASK disables multitasking. The task that calls ONETASK is always the task that will remain running after the call.
If multitasking is re-entered without calling SETTASK, the tasking picks up where it left off when ONETASK was called.
Or you can rephrase Current as this
SETTASK TskMapping Sets the task register. Contains implicit MULTASK Start
If clarity is the Goal, ( and clarity is always a good idea) the Assembler can simply support a choice of Mnemonic, with/without param and it then becomes like this
MULTASK TskMapping Param loads the task register, and Starts Tasking if not already started
With this, multitasking would be stopped (not just paused, though the distinction seems vague) using:
MULTASK #0
which is a little awkward, I think.
In the end, I think there should actually be six instructions:
GETTASK/SETTASK : get/set for the task register
GETTPC/SETTPC : get/set a specific task's PC (SETTPC is analogous to JMPTASK)
MULTASK/ONETASK : enable/disable multitask mode
These six instructions have very clear functional boundaries (no overlap), and provide everything(?) needed to manage tasks. But if that is too many instructions, so be it.
Also, one thing I noted/asked in my prior post was:
What if TLOCK is called before SETTASK is called? Is it ignored? Does multitasking get enabled at SETTASK, but immediately paused due to TLOCK?
If you go with Cluso's suggestion, where SETTASK is also an implicit TFREE, then this might not be an issue. But, based on Chip's comments, this is not how it works right now.
Above, Chip says the way it works now, is TaskMap changes pause until after TFREE, which then applies the new TaskMap
[" Right now, if you are multitasking and do TLOCK, you can do SETTASK without it taking effect until after TFREE."]
I got that. But that's not quite what I was asking. If multitasking is disabled (all task slots are %00) and TLOCK is called, what state is the cog in? I'm guessing that it is internally marked as paused/locked even though multitasking isn't really active to start with. And I'm guessing that if SETTASK is called in this state, multitasking becomes "active", but stays in the paused/locked state. At which point, Chip's comment (which you quoted) would still apply.
Regardless of whether my guesses are correct, or the cog acts in another way, the point is that the behavior is not obvious (but is hopefully consistent). I ask the question partly to show why I think the other approach is better, but also to better understand the rules if Chip chooses to keep things the way they are right now.
I'm not clear on what you are asking, but to me TLOCK is a hardware function, that (virtually) Pushes State MAP, and forces to ME=100%
That means it does not really matter to TLOCK what was in the register it pushed - it just pushes and -> 100%.
The TFREE does the converse, changes the 100% to whatever is poped.
Chips comment says a SETTASK writes to the stacked value, not the forced 100%
In HW operation, this is likely an output mask+Flag that simply hides/exposes the MAP register.
Whatever works is fine, but wwe do need to understandthe possible states...
notasks, multipletasks (setbySETASK), singletask ( setby TLOCK), multipletasks (set by TFREE)
And any other states possible (by TLOCK if no SETASK), (by TLOCK, then SETMAP, then TFREE).
Seems we only need 3 states which is whyI suggested SETASK also performed the TFREE internally.
I'll explain how this works using the current TLOCK/TFREE instructions.
SETTASK #0 and TFREE are the initial conditions on cog start.
Multi-task operation is when two conditions exist:
SETTASK established a task loop which includes more than one task
-and-
TLOCK has not been executed or TFREE has been executed
Single-task operation is when either of two conditions exist:
SETTASK established a task loop comprised of only one task
-or-
TLOCK has executed.
During single-task operation, instructions like WAITVID stall the pipeline.
During multi-task operation, instructions like WAITVID loop to themselves to allow other task slots to run.
When TLOCK executes, the task that executed TLOCK runs exclusively and the task slots don't advance, no matter what SETTASK established or what a SETTASK after a TLOCK establishes.
When TFREE executes, the task slots start cycling, whether they contain multiple tasks or just a single task.
Notably, a supervisor task could execute TLOCK, modify SETTASK, then TFREE again, completely invisible to the tasks on hold during TLOCK. Whole context switches could happen, replacing task variables, etc... during that time, the tasks on hold none the wiser.
Or, a task could be taken out of the list during one TLOCK, then returned later on during a subsequent TLOCK too.
Both of these seem like they are valid, and potentially useful in the right context. Not sure it makes sense to simplify further.
Since SETTASK can be set, but not acted upon during TLOCK, a task couldn't SETTASK itself out of the list, unable to TFREE either.
BTW: This has been bubbling up in my mind for a while. P2 is going to contain a basic hardware OS of sorts, capable of intricate management, sans software. Really, it's intriguing! Think of a shared P2. We could have a couple of people using the darn thing at the same time! Keyboards, displays, etc... No OS needed, though one might be recommended due to lack of memory management and protection, but still. These are quite advanced capabilities. Of course, that's just me musing over crazy things possible. Really, it's going to mean bigger programs that can do a lot without having the burden of an OS or other layers in the way.
Comments
As I understand it, as soon as TFREE is called, multitasking is re-enabled. The trailing instructions are executed in multitask mode. So, WAITxxx would behave however it normally behaves in multi-task mode. If they still do the self-jump (to avoid a pipeline stall), then that's what they would do.
So the other tasks do resume ok, while this one waits ?
ie It takes 2 clocks to flip-back tasks, and with no stall, other tasks fetch their opcodes ok.
(and the waitxx will become more granular, once tasking resumes)
The other tasks resume, while a WAITxxx trailing TFREE self-loops.
You brought up a good point: Are multitasking WAITxxx self-looping rules in effect during TLOCK? Yes, but I think they shouldn't be.
I've got to get the SERDES done before I can get to that.
I know we need better nomenclature for this.
Sounds good, but I would have expected a 100% task, to be cycle accurate on WAITxx without needing fixes ?
Was something else more subtle going on - was loop-to-self not a 1 cycle operation when done in a 100% task ?
A loop-to-self takes 4 clocks in single-tasking because three lower pipeline stages must be flushed. In 4-way tasking, nothing needs flushing, so those loops-to-self only take 1 clock, effectively. Stalling the pipeline until the event occurs is way more efficient for single-tasking (as you know).
Ahh, then certainly a good fix, and it ensures no-hidden-changes when moving from single to multi tasking.
Does this mean that a single task, when not locked, has the 4 cycle Wait-loops-to-self, but if you wanted the better granularity on a single-task, you could use the 100% slices command, (never restored), to flip modes on WAITxx ?
If SETTASK established multi-tasking (more than one task in the loop), then all tasks' 'waits' will loop to themselves, to accommodate the other tasks. The only time the pipeline is stalled for 'waits' is when you have only one task in the task loop, or you've locked a task temporarily.
While reading this thread, I thought of a possible naming solution. Then I read Seairth's thread and he is almost onto what I thought. How about...
TSINGLE & TMULTI (or TMULTID) ?
I disagree, SETTASK is fine, it's an atomic operation and it eliminates the need for *2* instructions.
TaskLOCK and TaskFREE are fine, and I can see them used quite extensively.
The problem with using STASK/MTASK is that then you force the (new) programmers to learn yet more hoops to jump through for making their code bug free. With just SETTASK, it's only a single instruction for adjusting the registers, which the chip then acts on.
Interesting idea. Right now, if you are multitasking and do TLOCK, you can do SETTASK without it taking effect until after TFREE. What you are proposing would be reversing the perspective a little bit, which I think makes more sense - multitasking ON or OFF, rather than thread LOCKed or FREEd. The one bugger is that you would have to do two instructions (SETTASK+MTASK) to start multitasking. I think the way we have it now is most efficient. We just need some good mnemonics.
current paradigm: TASKHLD/TASKREL
your paradigm: MULTASK/ONETASK
Most of the time you will be using the SETTASK without the TLOCK/TFREE instructions, so you've just caused evey multitasking program (if it is running in COG memory) to eat up a precious LONG for an instruction that really doesn't gain anything for anyone.
I guess I don't see the benefit of being able to set the task mask without activating it and then having an instruction to activate it/deactivate it other than using up a COG memory register every time you want to multi-task.
It is true that this would require two instructions to start multitasking. For the vast majority of applications, this likely means adding one instruction to the entire codebase. This hardly seems like a burden.
As for pedward's comment about forcing new programmers to learn more hoops, I think my approach is simpler to learn.
The current rule set:
The proposed rule set:
Which set of rules would be easier for programmers to learn?
Also, as I mentioned earlier, the current code for detecting whether in multi or single task mode has got to involve reading the task register and doing some sort of two-bit reduction/test to see if all fields have the same value. And then there's an addition one-bit test to see whether multitasking is paused. Even if the reduction/test is done once curing SETTASK and stored in a one-bit register, you still have a two-bit value that must be evaluated to handle multitasking mode. With the proposed approach, there is only the one-bit flag. The value of the task register has no affect on whether multitasking is enabled or not. This might not make much difference for gate count, but it might help critical path timing.
I think this just might boil down to semantics and that, currently, MULTASK is the default. In your case, ONETASK would be the default. We could use ONETASK/MULTASK in lieu of TASKHLD/TASKREL and be in the same place, never minding the default mode.
As far as finding out whether or not you are in multitasking, does it really matter, since you might find you are or aren't but on the next instruction the reality has changed?
SETASK (as now, sets up the tasks and enables task switching. $0 disables task switching)
TLOCK (as now, prevents this task from switching)
TFREE (as now, resumes task switching from where it left off)
But, if in TLOCK, then either TFREE resumes from where it left off, or SETASK sets up a new set of tasks and resumes.
That's kinda cool! Like reloading the cog and then switching from 4 wheel drive to 2 wheel drive!
Or you can rephrase Current as this
If clarity is the Goal, ( and clarity is always a good idea) the Assembler can simply support a choice of Mnemonic, with/without param and it then becomes like this
One fewer mnemonics to learn, and it is always clear where you are.
I think Chip said SETTASK queues within a ONETASK, and applies on TFREE release ?
That HW action probably works better with the mnemonic param option, where you would
...
ie exit has two choices, no param pop old mapping, with param, applies new mapping.
Q: Does that track what the hardware does, in the background .
This is one less line than SetSingle/LoadNewMap/ReleaseSingle
With this, multitasking would be stopped (not just paused, though the distinction seems vague) using:
which is a little awkward, I think.
In the end, I think there should actually be six instructions:
GETTASK/SETTASK : get/set for the task register
GETTPC/SETTPC : get/set a specific task's PC (SETTPC is analogous to JMPTASK)
MULTASK/ONETASK : enable/disable multitask mode
These six instructions have very clear functional boundaries (no overlap), and provide everything(?) needed to manage tasks. But if that is too many instructions, so be it.
What if TLOCK is called before SETTASK is called? Is it ignored? Does multitasking get enabled at SETTASK, but immediately paused due to TLOCK?
If you go with Cluso's suggestion, where SETTASK is also an implicit TFREE, then this might not be an issue. But, based on Chip's comments, this is not how it works right now.
Above, Chip says the way it works now, is TaskMap changes pause until after TFREE, which then applies the new TaskMap
[" Right now, if you are multitasking and do TLOCK, you can do SETTASK without it taking effect until after TFREE."]
I got that. But that's not quite what I was asking. If multitasking is disabled (all task slots are %00) and TLOCK is called, what state is the cog in? I'm guessing that it is internally marked as paused/locked even though multitasking isn't really active to start with. And I'm guessing that if SETTASK is called in this state, multitasking becomes "active", but stays in the paused/locked state. At which point, Chip's comment (which you quoted) would still apply.
Regardless of whether my guesses are correct, or the cog acts in another way, the point is that the behavior is not obvious (but is hopefully consistent). I ask the question partly to show why I think the other approach is better, but also to better understand the rules if Chip chooses to keep things the way they are right now.
That means it does not really matter to TLOCK what was in the register it pushed - it just pushes and -> 100%.
The TFREE does the converse, changes the 100% to whatever is poped.
Chips comment says a SETTASK writes to the stacked value, not the forced 100%
In HW operation, this is likely an output mask+Flag that simply hides/exposes the MAP register.
notasks, multipletasks (setbySETASK), singletask ( setby TLOCK), multipletasks (set by TFREE)
And any other states possible (by TLOCK if no SETASK), (by TLOCK, then SETMAP, then TFREE).
Seems we only need 3 states which is whyI suggested SETASK also performed the TFREE internally.
SETTASK #0 and TFREE are the initial conditions on cog start.
Multi-task operation is when two conditions exist:
SETTASK established a task loop which includes more than one task
-and-
TLOCK has not been executed or TFREE has been executed
Single-task operation is when either of two conditions exist:
SETTASK established a task loop comprised of only one task
-or-
TLOCK has executed.
During single-task operation, instructions like WAITVID stall the pipeline.
During multi-task operation, instructions like WAITVID loop to themselves to allow other task slots to run.
When TLOCK executes, the task that executed TLOCK runs exclusively and the task slots don't advance, no matter what SETTASK established or what a SETTASK after a TLOCK establishes.
When TFREE executes, the task slots start cycling, whether they contain multiple tasks or just a single task.
Notably, a supervisor task could execute TLOCK, modify SETTASK, then TFREE again, completely invisible to the tasks on hold during TLOCK. Whole context switches could happen, replacing task variables, etc... during that time, the tasks on hold none the wiser.
Or, a task could be taken out of the list during one TLOCK, then returned later on during a subsequent TLOCK too.
Both of these seem like they are valid, and potentially useful in the right context. Not sure it makes sense to simplify further.
Since SETTASK can be set, but not acted upon during TLOCK, a task couldn't SETTASK itself out of the list, unable to TFREE either.
BTW: This has been bubbling up in my mind for a while. P2 is going to contain a basic hardware OS of sorts, capable of intricate management, sans software. Really, it's intriguing! Think of a shared P2. We could have a couple of people using the darn thing at the same time! Keyboards, displays, etc... No OS needed, though one might be recommended due to lack of memory management and protection, but still. These are quite advanced capabilities. Of course, that's just me musing over crazy things possible. Really, it's going to mean bigger programs that can do a lot without having the burden of an OS or other layers in the way.