Shop OBEX P1 Docs P2 Docs Learn Events
Spin2 + PASM (regexec); Erratic Behavior with Case Statement [Solved] — Parallax Forums

Spin2 + PASM (regexec); Erratic Behavior with Case Statement [Solved]

DarkInsanePyroDarkInsanePyro Posts: 31
edited 2021-09-18 18:34 in Propeller 2

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

  • evanhevanh Posts: 15,170

    @DarkInsanePyro said:
    ... 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?

    Example? Many early posts in the prop2 forum are pure Pasm2, from before Spin2 being developed.

  • @evanh said:

    @DarkInsanePyro said:
    ... 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?

    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.

  • evanhevanh Posts: 15,170

    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. :(

  • RaymanRayman Posts: 13,850

    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.

  • @Rayman said:
    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.

  • DarkInsanePyroDarkInsanePyro Posts: 31
    edited 2021-09-18 00:07

    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

    CON
      _xtlfreq      = 20_000_000
      _clkfreq      = 20_000_000
    
      DEBUG_LOG_SIZE = 1000000
      FILL_LONG     = $DEAD_BEEF
    
    PUB Main() | cnt
      cnt := 0
      regload(@assembly)
    
      repeat
        debug(udec(cnt++), udec(PR0), uhex_reg_array($129, 8))
        case cnt
          0: debug("zero")
          1: debug("one")
        waitms(10)
    
    DAT
    assembly                word asm_start, asm_end-asm_start-1
                            org $129
                            '       $129       $12A       $12B       $12C       $12D       $12E       $12F
    asm_start               long    FILL_LONG, FILL_LONG, FILL_LONG, FILL_LONG, FILL_LONG, FILL_LONG, FILL_LONG
    asm_end
                            fit $130
    

    Debug

    Cog0  INIT $0000_0000 $0000_0000 load
    Cog0  INIT $0000_0D6C $0000_10E0 jump
    Cog0  cnt++ = 0, PR0 = 979, $129 = $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $0447_AA1F
    Cog0  one
    Cog0  cnt++ = 1, PR0 = 979, $129 = $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $0447_AA1F
    Cog0  zero
    Cog0  one
    Cog0  cnt++ = 2, PR0 = 979, $129 = $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $0447_AA1F
    Cog0  zero
    Cog0  one
    Cog0  cnt++ = 297, PR0 = 979, $129 = $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $0447_AA1F
    Cog0  zero
    Cog0  one
    Cog0  cnt++ = 298, PR0 = 979, $129 = $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $DEAD_BEEF, $0447_AA1F
    01000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_11111000_00010000_11111100_00001000_ [... this goes on for a long time]
    

    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.

  • RaymanRayman Posts: 13,850

    Maybe it's just the last long doing it? Maybe should be "Fit $129"? We're not supposed to use $130...

  • @Rayman said:
    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):

    DAT
    assembly                word asm_start, asm_end-asm_start-1
                            org $12F
                            '       $12F
    asm_start               long    FILL_LONG
    asm_end
                            fit $130
    

    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.

  • RaymanRayman Posts: 13,850

    Well, guess it could be that Spin2 needed more room and the docs are wrong...

  • @Rayman said:
    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:

    '*********************************
    '*  Interpreter - cog registers  *
    '*********************************
    '
                    org     $124            'user area below
    cog_code                                'start of cog code
    

    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. :smile:

  • RaymanRayman Posts: 13,850

    Guess you figured it out! I made a note in the Spin2 docs. Chip will probably fix it one day...

  • @Rayman said:
    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!

Sign In or Register to comment.