Purely out of curiosity I decided to do an experiment into the nature of one Propeller Cog's temporal relationship with another Cog. Is the instruction executions of two (or more) Cogs constant, or can the phase between them be changed?
One thing we can find out easily from the datasheet and manual for the Propeller is "hub instructions take from 8 to 23 cycles to complete". Interesting if you think hard enough about it. If your code uses many Cogs and calls hub instructions many times per Cog, then over time it is quite possible that events that you need to remain synchronised between Cogs can drift out of the idea pattern that you intended, due to the way your program branches or loops. Certain applications might require a means to re-synchronise two or more Cogs to adhere to some external specification that you wish to adhere to.
While the Propeller's instruction execution timing is certainly deterministic (it will always behave in exactly the same manner each time a program is run), it is not necessarily intuitive or easy for the programmer to predict, once a program exceeds a certain complexity. Sometimes you just have to run the program and see what happens.
The experiment I wanted to conduct was intended to determine whether a programmer can deliberately influence the phase difference between two or more Cogs in a Propeller microcontroller. There are many reasons to want to do this, including high-speed signalling, analog-digital conversion, audio generation and so on...
One thing we can find out easily from the datasheet and manual for the Propeller is "hub instructions take from 8 to 23 cycles to complete". Interesting if you think hard enough about it. If your code uses many Cogs and calls hub instructions many times per Cog, then over time it is quite possible that events that you need to remain synchronised between Cogs can drift out of the idea pattern that you intended, due to the way your program branches or loops. Certain applications might require a means to re-synchronise two or more Cogs to adhere to some external specification that you wish to adhere to.
While the Propeller's instruction execution timing is certainly deterministic (it will always behave in exactly the same manner each time a program is run), it is not necessarily intuitive or easy for the programmer to predict, once a program exceeds a certain complexity. Sometimes you just have to run the program and see what happens.
The experiment I wanted to conduct was intended to determine whether a programmer can deliberately influence the phase difference between two or more Cogs in a Propeller microcontroller. There are many reasons to want to do this, including high-speed signalling, analog-digital conversion, audio generation and so on...
The Experiment...
Aim: to start up two Cogs using Spin code (by calling the cognew function twice) and then influence the phase (for want of a better word) difference between execution cycles on the two Cogs.
In other words, when two Cogs are made to toggle a pin each, is it possible to force the Cogs to toggle at the same time? Equally, is it possible to make them toggle the pins out of synchrony with each-other?
I needed a simple way to test the theorem. If I have one Cog running a simple tight-loop that toggles a pin each iteration, then I should be able to run another Cog that does basically the same thing but alter it slightly so that before starting the loop add a delay until a time when the second Cog is "in phase" with the first Cog.
If it works then both Cogs should end up toggling the pins on the same global clock cycle.
I wrote the following code to try this out...
CON
_clkmode = RCFAST
PUB Main
cognew(@METRONOME, 1)
cognew(@MODULATED, 0)
DAT
'FIRST COG's task...
org 0
METRONOME
mov dira, metronome_pin
mov outa, #0
mov counta, #256
:a xor outa, metronome_pin
djnz counta, #:a
waitpeq $, #0
metronome_pin long |< 0
counta res 1
fit
'SECOND COG's task...
org 0
MODULATED
or dira, modulated_pin
mov outa, #0
mov countb, #64 'number of iterations before the delay
mov countc, #64 'number of iterations after the delay
:b xor outa, modulated_pin 'tight loop toggling a pin
djnz countb, #:b
' << cleverly chosen time delay here >>
mov time, cnt
add time, #17 'minimum value is 9. Choose this carefully in order to synchronise your cogs
waitcnt time, 0
'time delay code ends
'NOW the two cogs should be IN PHASE! :)
:c xor outa, modulated_pin 'tight loop toggling a pin
djnz countc, #:c
waitpeq $, #0 'sleep forever
modulated_pin long |< 1
time res 1
countb res 1
countc res 1
fit
This code isn't very complex, it should run on a bare chip and uses the internal timer. In fact using the internal timer is recommended because it the slow clock rate makes it easier to measure the exact timings of the pin states using a logic analyser! I used my marvellous "Saleae Logic" (go buy one right now, seriously).
The following screenshots show the output of the two Propeller IO pins, but first a little explanation of the code is in order, since Propeller Assembly language (PASM) might be an alien language for some readers...
I have created a pair of Cog tasks named METRONOME and MODULATED. Cog 1 begins running the METRONOME code first, followed shortly afterwards by Cog 0 being restarted and running the MODULATED task.
Each Cog task toggles a different pin on the Propeller. METRONOME starts first and toggles pin P0. MODULATED starts a moment later and toggles pin P1 as soon as it starts, then takes a short delay where it stops toggling. After the delay it resumes toggling again but this time it's instruction execution "phase" has been adjusted to be synchronised with the METRONOME task.
Because Spin is an interpreted language, it is not obvious how many clock cycles elapse between METRONOME starting and MODULATED starting. We know that both cogs will eventually be running simultaneously but there will be some relatively large time offset between them.
Take a look at the Logic analyser's recordings of P0 and P1 over time...
What we see here is that during the first loop ("b") in the MODULATED task, the pin toggling is out of phase with the METRONOME task on the other Cog. For my purposes I wanted to be able to rectify this so that future pin toggling happens in phase with the METRONOME task.
We have a number of facts at our disposal now...
1) Both Cogs are running at the same clock frequency.
2) Propeller Cogs execute XOR instructions and non-jumping DJNZ instructions in 4 clock cycles each. So each iteration of the toggling loops take 8 clock cycles each - this is a fixed value.
3) Hub instructions can elapse an amount of time that is an odd number of clock cycles.
Given these facts we can logically influence the phase difference between the instruction execution time on two separate cogs by as little as 1 clock cycles (remember that most instructions are 4 clocks in duration).
Turns out that the WAITCNT instruction gives you single-clock-cycle timing resolution, although this isn't explicitly stated in the datasheet or the manual for the Propeller P8X32A chip, as far as I am aware.
The "#17" seen here is the offset (in clock cycles) into the future to make the Cog wait until. Seventeen is arbitrary for this experiment. Using numbers smaller than 9 will cause the waitcnt mechanism to wrap around it's 32 bits worth of timing counter and this would cause many many hours of fruitless inactivity, so don't use numbers less than 9.
For this super-simple experiment 17 is good enough to sync up these two very tight loops. If your loops take longer then you'll need to select a delay that allows your slower loop to wait until the faster loop to get back into a state that can be "joined" by the slow loop. The delay could be up into the thousands or millions if you're using very long loops or using high clock speeds with external crystals and high PLL multipliers.
This isn't a particularly advanced topic though, I just went into this amount of detail because it's quite good fun to look deeply into the Propeller anyway, and who knows, it might be useful to someone somewhere.
If this is useful to you then please leave a comment and let me know.
Good luck :)
The following screenshots show the output of the two Propeller IO pins, but first a little explanation of the code is in order, since Propeller Assembly language (PASM) might be an alien language for some readers...
I have created a pair of Cog tasks named METRONOME and MODULATED. Cog 1 begins running the METRONOME code first, followed shortly afterwards by Cog 0 being restarted and running the MODULATED task.
Each Cog task toggles a different pin on the Propeller. METRONOME starts first and toggles pin P0. MODULATED starts a moment later and toggles pin P1 as soon as it starts, then takes a short delay where it stops toggling. After the delay it resumes toggling again but this time it's instruction execution "phase" has been adjusted to be synchronised with the METRONOME task.
Because Spin is an interpreted language, it is not obvious how many clock cycles elapse between METRONOME starting and MODULATED starting. We know that both cogs will eventually be running simultaneously but there will be some relatively large time offset between them.
Take a look at the Logic analyser's recordings of P0 and P1 over time...
What we see here is that during the first loop ("b") in the MODULATED task, the pin toggling is out of phase with the METRONOME task on the other Cog. For my purposes I wanted to be able to rectify this so that future pin toggling happens in phase with the METRONOME task.
We have a number of facts at our disposal now...
1) Both Cogs are running at the same clock frequency.
2) Propeller Cogs execute XOR instructions and non-jumping DJNZ instructions in 4 clock cycles each. So each iteration of the toggling loops take 8 clock cycles each - this is a fixed value.
3) Hub instructions can elapse an amount of time that is an odd number of clock cycles.
Given these facts we can logically influence the phase difference between the instruction execution time on two separate cogs by as little as 1 clock cycles (remember that most instructions are 4 clocks in duration).
Turns out that the WAITCNT instruction gives you single-clock-cycle timing resolution, although this isn't explicitly stated in the datasheet or the manual for the Propeller P8X32A chip, as far as I am aware.
The Solution...
mov time, cnt
add time, #17
waitcnt time, 0
The "#17" seen here is the offset (in clock cycles) into the future to make the Cog wait until. Seventeen is arbitrary for this experiment. Using numbers smaller than 9 will cause the waitcnt mechanism to wrap around it's 32 bits worth of timing counter and this would cause many many hours of fruitless inactivity, so don't use numbers less than 9.
For this super-simple experiment 17 is good enough to sync up these two very tight loops. If your loops take longer then you'll need to select a delay that allows your slower loop to wait until the faster loop to get back into a state that can be "joined" by the slow loop. The delay could be up into the thousands or millions if you're using very long loops or using high clock speeds with external crystals and high PLL multipliers.
The Conclusion...
It is possible to change the phase relationship between two or more Cogs that are both running assembler code. This is potentially very handy if you're having some weird timing issues on your multi-Cog applications. If in doubt, get the logic analyser out!This isn't a particularly advanced topic though, I just went into this amount of detail because it's quite good fun to look deeply into the Propeller anyway, and who knows, it might be useful to someone somewhere.
If this is useful to you then please leave a comment and let me know.
Good luck :)