Dr Jefyll wrote:
Hugh, can you elaborate about the T flag, please? According to your text document, "The T flag is set to indicate that a task-switch is needed," and you illustrate how the NEXT macro would include a BTC instruction to test T.
Would it be a timer-triggered Interrupt Service Routine that sets T, and/or might T be set by other means? Also, might T be tested at times other than when passing through NEXT?
-- Jeff
Well, I haven't written a multi-tasking OS, or had any experience in using one, so I'm speculating here.
Yes, the ISR for a heartbeat-timer can set T to indicate that it is time for a task switch.
There may be other cases in which T gets set. For example, an ISR that is inputting data into a circular buffer may set T if it is close to filling the buffer so the task that empties the buffer can get run --- presumably the OS is smart enough to figure out that this is the highest priority task to run.
One reason for doing a task-switch in NEXT, rather than in the middle of a primitive, is that less context needs to be saved. The A Y and P registers don't have to be saved because these are never used to pass data between primitives. This would be more important in a processor that had a lot of registers (the AVR has 16 registers and it has a T flag, which is where I got the idea). It is not very important on the 65VM02 because we have very few registers anyway --- there may be a lot of direct-page data associated with the task, but each task has its own direct-page so this doesn't need to get saved either. If the 65VM02 has any advantage over other processors (a somewhat dubious claim), then that advantage would be fast task-switches and low interrupt latency.
Another reason for doing a task-switch in NEXT, rather than in the middle of a primitive, is that this should be safer. Isys Forth would store data underneath the data-stack, and then cover up the data with DEX --- this was done to allow peephole optimization (functions that did INX did so at the start, and functions that did DEX did so at the end, so DEX and INX were juxtaposed and could be eliminated). This is not an issue on the 65VM02 however, because I can just make the rule that primitives are not to be written that store data underneath the data-stack (notice how all my primitives that do DEX have the DEX at the start).
I got the idea of the T flag from the AVR, but the 65VM02 is different because it has a lot fewer registers. It is possible that the T flag is not needed on the 65VM02 and I can just drop that feature. It is slowing down NEXT a lot. Maybe a preemptive task-switch in the middle of a primitive is okay.
BTW: There is a bug in CMOVE --- I'm surprised that none of you 6502 have pointed that out yet.
I've been thinking about the idea of a coprocessor that handles all I/O. It would have its own main-memory, so there is no bus contention. It would have access to the alternate-bank though, so any instructions in either processor that access the alternate-bank have to lock out the other processor from accessing the alternate-bank at the same time. I'm somewhat dubious of the idea of a coprocessor though. A better use of resources might be to just upgrade the 65c02's weak support for ISRs. When doing an interrupt, after saving the registers, D should be set to 0 and A should be set to an indicator of what caused the interrupt. This way the ISR doesn't have to poll the I/O ports to figure out what caused the interrupt and what needs to be done --- it could just do a JVM to get to the correct ISR --- so you would effectively have 128 different interrupts, rather than just one like on the 65c02 (I got this idea from the Z80).
Finally, let me say that I always thought the 6502 versus Z80 argument was foolish. There is a lot to be learned from the Z80. Similarly, there is a lot to be learned from the AVR and various other processors. It is not a good idea to become too focused on the 6502 as if it were a religion.