Simple coroutine trick, thanks to pipelining.
Phil Pilgrim (PhiPi)
Posts: 23,514
In the thread "Self-modified code execution timing" posted by lucidman, the question came up of what happens when a jmpret (i.e. call) goes to a location containing a ret. Because of pipelining, the new return address is not written until after the value of the target location is fetched, which means the ret jumps to the return address written there from the previous call.
This is, indeed, a serendipitious property! It means that a simple task switcher for cooperative coroutines can be written in one line, without requiring additional memory. To test the theory, I wrote the following program that includes two tasks: one for turning on an LED, another for turning it off.
And it works! The LED flashes on and off with a 1/2-second period.
-Phil
This is, indeed, a serendipitious property! It means that a simple task switcher for cooperative coroutines can be written in one line, without requiring additional memory. To test the theory, I wrote the following program that includes two tasks: one for turning on an LED, another for turning it off.
[b]CON[/b] [b]_clkmode[/b] = [b]xtal[/b]1 + [b]pll[/b]16x [b]_xinfreq[/b] = 5_000_000 [b]PUB[/b] start [b]cognew[/b](@switcher, 0) [b]DAT[/b] switcher [b]movs[/b] switch,#task1 'Initialize switch return address. [b]mov[/b] [b]dira[/b],_0001_0000 'Set A16 (LED) as an output. [b]mov[/b] cntnxt,[b]cnt[/b] 'Initialize time delay. [b]add[/b] cntnxt,delta task0 [b]mov[/b] [b]outa[/b],_0001_0000 'Turn LED on. [b]waitcnt[/b] cntnxt,delta 'Wait 1/4 sec. [b]call[/b] #switch 'Switch to task1. [b]jmp[/b] #task0 'Loop back when task1 returns. task1 [b]mov[/b] [b]outa[/b],#0 'Turn LED off. [b]waitcnt[/b] cntnxt,delta 'Wait 1/4 sec. [b]call[/b] #switch 'Switch back to task0. [b]jmp[/b] #task1 'Loop back when task0 returns. switch 'CALL stores new return address at return, switch_ret [b]ret[/b] ' and returns to previous caller. _0001_0000 [b]long[/b] $0001_0000 'Bit 16. delta [b]long[/b] 20_000_000 '1/4 sec at 80MHz. cntnxt [b]res[/b] 1 'Save area for next cnt value.
And it works! The LED flashes on and off with a 1/2-second period.
-Phil
Comments
Without your explaination, I never would have understood whats happening from just looking at the code
I suppose this would also work with three (or n) tasks. A very simple round-robin scheduler?
Mark
On closer examination, one RET can only handle flipping between two coroutines. More complex flipping would require a second RET, as in: task0 calls Switch_A, task1 calls Switch_B, task2 calls Switch_A, etc. I haven't fully thought this through, but it could get quite fancy.
Mark
-Phil
I looked at a multi-switch scheme like you suggested, but I think it's doomed. What will happen is that for any switch called by two or more tasks, the intended round-robin cycling will lapse into a ping-pong between the last two to call it, and other tasks in the cycle that use other switches will get left out.
This isn't to say that a round-robin scheme that uses jmprets can't exist — just not one that uses this pipelining paradigm.
-Phil
So to do three, the "scheduler" looks like:
topofloop:
JMPRET rendezvouspt1
JMPRET rendezvouspt2
JMPRET rendezvouspt3
JMP topofloop
To deschedule a task, change the JMPRET to a NOP. To schedule a task, fill in the JMPRET with the appropriate address.
This takes a cell more, but is "clean" and easily expandable to more coroutines...
I should consider this an even more interesting application of JMPRET...
Still very, very, cool though.
Chip Gracey
Parallax, Inc.