Spin2 + PASM (regexec); Erratic Behavior with Case Statement [Solved]
Good day all. After getting very familiar with P1 I am now diving into P2 and have gotten pretty far through my research but I am running into an issue and I haven't been able to decipher for the past few days. The following code is a distilled example from a significantly larger project that this issue was discovered.
Issue
The simplified version is, I have a buffer essentially aligned to the end of what the manual says is free to use registers in the Cog ($12F is the last register). If I write to these last ~10 registers, and seemingly if the Spin2 code uses a CASE statement, the Cog will "crash" or usually just start to execute data as if code (exciting results!). No doubt I am touching something I shouldn't be, but I haven't put my finger on it yet.
Remove the case statement and everything is fine. I have not run into other Spin2 statements that cause similar behavior.
Example
CON _xtlfreq = 20_000_000 _clkfreq = 20_000_000 PUB Main() | cnt cnt := 0 regexec(@assembly) repeat call(#buf_test_long) debug(udec(cnt++), udec(PR0)) case PR0 0: debug("zero") 1: debug("one") waitms(10) DAT assembly word asm_start, asm_end-asm_start-1 org asm_start _ret_ mov offset, #0 ' last debug entry from Spin2 test above: ' Cog0 cnt++ = 420, PR0 = 421 buf_test_byte getrnd value altsb offset, #buffer setbyte value incmod offset, #((buffer_end - buffer) * 4 - 1) _ret_ mov PR0, offset ' last debug entry from Spin2 test above: ' Cog0 cnt++ = 105, PR0 = 106 buf_test_long getrnd value altd offset, #buffer mov 0-0, value incmod offset, #(buffer_end - buffer - 1) _ret_ mov PR0, offset asm_end value res 1 offset res 1 ' limit buffer to 512 bytes for demo filler res $130 - 512/4 ' use the remaining space as R/W memory, if I subtract 10 longs ' it appears to always run and not crash buffer res $130 - buffer ' - 10 buffer_end fit $130
Observations
- I haven't found anything in spin2_interpreter.spin2 giving any indication of what this is
- Debugger enabled or not appears to be the same
- Data values written into ~$120 to ~$12F appears to influence how fast the Cog crashes
Environment
OS: Windows 10
Compiler: Prop Tool 2.5.3
Hardware: P2 Eval Board (Parallax), USB powered
Extra
In the larger project, oh man the symptoms were crazy. From launching random Cogs (per debugger) to just spamming random gibberish on my USART code, and bizarre debug output. Even saw debug output from dead code. This project uses SmartPins, multiple interrupts, and several layers of Spin2 code. The distilled version is a bit more behaved. It just simply stops running.
Side question. A lot of examples and some of the user's code here offset their code within the Cog instead of just aligning to the top ($000) for regexec/etc.. Is there a reason why?
Comments
Example? Many early posts in the prop2 forum are pure Pasm2, from before Spin2 being developed.
Example being the one from "Parallax Spin2 Documentation v35m" section "CALLING PASM FROM SPIN2". But it outright states anywhere from $000 to $130-size is fine. I've been poking at this issue for a while and was getting paranoid at all the little things that I used to be sure about. cgracey has an ADC example that starts at $B0 mixed with Spin2. I thought there was a serial driver as well but, well, I'm not sure where it is too out of it tonight. But now that I am not looking for specific things, like interrupt snippets, I see all of the codebases using default/$000 origin.
Oh, I see. I've not used the Spin docs much.
Regload/regexec() isn't supported in FlexSpin. So can't compare on that front.
I'm not sure "RES" is allowed in this context... Not 100%, but that could be the problem.
RES is used to define locations that are not in HUB memory when launching a cog with code.
But here, you are not starting a new cog, you are loading code into the Spin2 cog and running it.
This is an interesting, but a bit complex way to load assembly routines into the free space of the Spin2 cog and then call it.
BTW: I think you can start this in different places as a way to have several assembly subroutines loaded and able to be called.
I can provide some more details when I get home if needed but I did verify this using debug statements to print the various addresses and they appear to land where expected. E.g., buffer would be some some address near the end of the the register memory, and debug(uhex_long(#buffer_end)) would print out $130 (last register + 1) as expected. I even did a debug uhex_reg_array on the boundaries of the buffer manually (e.g., debug(uhex_reg_array($129, #2))) to verify the write occurs exactly on the last register but not afterwards. Based on how the manual states the address counter still increments but doesn't introduce new data, it appears to work as expected. Off the top of my mind debug statements in this post, not copy paste so typos may exist.
I had thought about the loading independent routes separately. A bit odd though since each routine will need to coordinate with the previous so they don't overlap. Also everything would have to be relative addressed. But might be an interesting tangent project..
Seems like a funny bug in the Spinterpreter. Check the source for the CASE-related opcodes, could be doing some out of bounds access or smth.
Then again, relying on the cog memory area not getting clobbered while executing high level code seems like questionable practice.
Right now the manual indicates the memory is persistent/shouldn't be clobbered but maybe there is something unintentional going on. In fact, if the code doesn't persist doesn't it negate the purpose of the call statement? New example, even simpler. What I found interesting is it doesn't clobber any of the memory, at least not immediately unless it crashes right as it is written then read.
Sample
Debug
I'll look into the spin2_interpreter more... not familiar with a few mechanisms yet. All ears if you have a recommendation on debugging it. I am considering running PASM direct of the interpreter and inserting my own bytecode where the test code is.
Edit: I just noticed cnt jumps from 2 to 297. Bizarre.
Maybe it's just the last long doing it? Maybe should be "Fit $129"? We're not supposed to use $130...
Fit as far as I understand and tested, tests for the current address counter, which should be the next register after the last instruction. Thus checking if $130 since last register was $129. This verifies the behavior (fit $129 fails, fit $130 passes):
Also, keeping with the single long scheme I found the following results by adjusting the org statement from the snippet earler:
$125 OK
$126 Relaunches Cog0 at incorrect address/stack. Runs without stopping but incorrectly.
$127 Runs one iteration
$128 Runs one iteration
$129 Runs 5 iterations, incorrectly
$12A - $12F Runs fine.
Well, guess it could be that Spin2 needed more room and the docs are wrong...
Gosh dangit. Last night I was digging into it but kept running into parity issues between the interpreter source code and the actual compiler's opcodes. Turns out I'm referencing an older version of the interpreter. Is there any version control of this file I can get the latest from? Anyhow, downloaded PNut and checked the one it ships with, found huge differences, and found this interesting tidbit:
Agreed that there is incorrect info in the manual. At some point in the development they reduced the user-space/free cog memory to $124 registers it appears. The only reason I kept digging is I didn't want to blindly avoid a region of memory especially when it is code specific on when it fails. I realized this since I dumped unmodified Cog register memory and ran it through a disassembler and it looked a lot like interpreter code. Guess I know why now. Oh well, now I am more... intimate with the interpreter.
Guess you figured it out! I made a note in the Spin2 docs. Chip will probably fix it one day...
Thanks for adding a note about that in the doc for me. I thought I heard there is offline documentation being worked on that is official/formal. Either way, good to have it noted before someone else goes down that rabbit hole. Thanks for the help!