For Newcomers: Spying on Your Own Code (without special tools)
JonnyMac
Posts: 9,194
I often see newcomers struggle trying to debug their code by adding serial output for PST. Even though the FDS object is running in another cog, it can create bottleneck with its ridiculously small buffers (16 bytes) and your code to move values into the buffer takes time, anyway.
For almost every project I launch a monitor cog that is used to spool global values to PST so I can keep an eye on things. That's great, but what if we want to spy on a Spin method that we're developing to run in its own cog? How do we watch its local variables (which are not visible to the monitor cog)?
I'm not as patient as some so I won't get too deep in this, but the key is understanding how to use the @ (address of) operator. This operator provides the location in memory of the variable that it's attached to. Since the monitor cog can only see global values and its own locals what we need to do is create global a global variable that holds a pointer to (the address of) the first local variable in the cog under test. If we know the address of the first we can offset to get any others -- all local variables are longs and contiguous on the stack (where local variables are stored while in use).
Here's a little demo method that is designed to run in it's own cog via cognew.
The purpose of this cog is to randomize four local variables (which is pointless other than for this demonstration). It also toggles an output pin (heartbeat). The first parameter passed is the pin # to toggle, the second is the # of milliseconds used in the loop (speed of updates), the third -- key (and temporary) -- is that address in the global area space where we will place the address of our local variables.
The first line of the method puts the address of this method's local variables into the global address we passed to it; then it drops into a loop randomizing four values at the rate we specified.
Here's how we launch this cog:
As you can see we will be toggling the pin defined by LED1, every 333ms, and we will pass it the address of locals1 -- this is where the cog will put the address of its local variables. Every Spin cog needs stack space and we pass the address of stack1 (an array of longs) for that.
Your monitor cog has to know what its monitoring. Here's the code from the attached demo:
As you can see, the monitor cog *knows* that we have two randomizer cogs and the global values that hold the respective pointers. IMHO, some try to make code too generic -- there are times when getting specific is useful, as in testing for development. After you've wrung out method code (e.g., the randomizer) you can remove the parameter for passing the global address and the line that deals with that.
The key is this bit of code
...as it is retrieving a long value (all locals are longs). The first element when we're using this configuration is the base address; in this case we're accessing the variables pointed to by locals1. The index (idx) allows us to offset from the base. This means that we have to understand the structure of the variables in our Spin cog, but we wrote the code so we should.
The point of all this is to be able to see what's going on with values in your code modules without affecting the performance of those modules by the insertion of serial outputs statements.
For almost every project I launch a monitor cog that is used to spool global values to PST so I can keep an eye on things. That's great, but what if we want to spy on a Spin method that we're developing to run in its own cog? How do we watch its local variables (which are not visible to the monitor cog)?
I'm not as patient as some so I won't get too deep in this, but the key is understanding how to use the @ (address of) operator. This operator provides the location in memory of the variable that it's attached to. Since the monitor cog can only see global values and its own locals what we need to do is create global a global variable that holds a pointer to (the address of) the first local variable in the cog under test. If we know the address of the first we can offset to get any others -- all local variables are longs and contiguous on the stack (where local variables are stored while in use).
Here's a little demo method that is designed to run in it's own cog via cognew.
pri random_4x(hbpin, ms, spypntr) | value[4], t, idx '' Randomize four local values '' -- hbpin is heartbeat pin to show cog is running '' -- ms is loop timing in milliseconds '' -- spypntr is global address used to hold address of local variables long[spypntr] := @value[0] ' report start of locals t := cnt ' sync timer repeat toggle(hbpin) repeat idx from 0 to 3 value[idx] := ?seed ' randomize value[idx] waitcnt(t += (ms * MS_001)) ' update every ms milliseconds
The purpose of this cog is to randomize four local variables (which is pointless other than for this demonstration). It also toggles an output pin (heartbeat). The first parameter passed is the pin # to toggle, the second is the # of milliseconds used in the loop (speed of updates), the third -- key (and temporary) -- is that address in the global area space where we will place the address of our local variables.
The first line of the method puts the address of this method's local variables into the global address we passed to it; then it drops into a loop randomizing four values at the rate we specified.
Here's how we launch this cog:
cognew(random_4x(LED1, 333, @locals1), @stack1)
As you can see we will be toggling the pin defined by LED1, every 333ms, and we will pass it the address of locals1 -- this is where the cog will put the address of its local variables. Every Spin cog needs stack space and we pass the address of stack1 (an array of longs) for that.
Your monitor cog has to know what its monitoring. Here's the code from the attached demo:
pri monitor | t, idx ' Note: This is used to report global values or use global pointers to ' reveal locals in other methods term.start(RX1, TX1, %0000, 115_200) pause(10) term.tx(CLS) t := cnt repeat term.tx(HOME) ' print values from 1st repeat idx from 0 to 3 ' display four values term.hex(idx, 1) ' hex faster than dec for 1 digit term.tx(" ") term.dec(long[locals1][idx]) ' show random # term.tx(CLREOL) ' clean-up line term.tx(CR) ' print values from 2nd term.tx(CR) repeat idx from 0 to 3 term.hex(idx, 1) term.tx(" ") term.dec(long[locals2][idx]) term.tx(CLREOL)' term.tx(CR) waitcnt(t += (100 * MS_001)) ' update every 100ms
As you can see, the monitor cog *knows* that we have two randomizer cogs and the global values that hold the respective pointers. IMHO, some try to make code too generic -- there are times when getting specific is useful, as in testing for development. After you've wrung out method code (e.g., the randomizer) you can remove the parameter for passing the global address and the line that deals with that.
The key is this bit of code
long[locals1][idx]
...as it is retrieving a long value (all locals are longs). The first element when we're using this configuration is the base address; in this case we're accessing the variables pointed to by locals1. The index (idx) allows us to offset from the base. This means that we have to understand the structure of the variables in our Spin cog, but we wrote the code so we should.
The point of all this is to be able to see what's going on with values in your code modules without affecting the performance of those modules by the insertion of serial outputs statements.
Comments
Since my Windows/Parallels on the iMac is so terribly slow (like about 1/2+ to boot up) I'd gone to BST. So was hoping there would be a way to monitor Spin variables.
This will now replace ViewPort except for when one NEEDS to look at the high-speed PASM signaling. Will have to try this out.
Is it necessary to specify spypntr as a pointer to longs here?
Technically, the address will fit into a word. That said, I don't tend to use words and bytes unless there is a very specific reason. If you declared your global storage variables as word, you would of course have to modify that code to write to the word at the specified address.
I will give your method a try when I next get stuck.
Cheers
Richard
I want to point to a freeware-tool that does a similar thing
http://hannoware.com/ezlog/
You could say ezlog is the tiny brother of ViewPort; a very sohpisticated debugsoftware which needs some time to learn how to use it.
Learning how to use ezlog still needs a little time. (I guess 10 to 30 minutes by startng with the demo, then modifying the demo, then integrating the debug-object into your own code).
Hanno has no obvious link to ezlog on his website.
@Hanno: as ezlog is "out in the internet" would you mind to add a link on your website to it?
best regards
Stefan
I'm looking forward to trying this out.
Thanks, I'll have a look. With my style I never found ViewPort very useful (this is not an indictment of the program; many find it extremely useful) -- most of the time I just need to monitor a few variables so do it in the most straightforward way possible. I think, too, that newcomers should use the Propeller as well as they can without relying on external tools. I'm constantly surprised when someone asks "How fast is this code?" when the Propeller provides an easy means of timing itself. Again, this is my opinion, but I believe when one understands the Propeller it is easier to exploit external tools -- so lean to work with the Propeller without them first.
Follow-up: I downloaded and tried to run EzLog with my program. I had doubts because I want to spy on a method that gets launched into two cogs. When I ran the EzLog program my anti-virus software said it was infected. I let it run anyway. It did not work on Auto setting (couldn't find the Propeller, even though I only have one connected). Using manual I did get connected by the display does not update. I re-downloaded the program allowing it only to start one randomizer cog and it did work.
EzLog seems like a useful tool if you want to spy on one isolated set of variables. In work, I often want to look at globals and locals in various places in the program. Yes, this creates a bit of work for me to setup the monitor method, but I'm okay with that. By writing it myself I get exactly what I want in the format I want.
I have not tried it yet, but it sounds very interesting. Thanks for sharing.
Bruce