Shop OBEX P1 Docs P2 Docs Learn Events
Working With CRC (Inline PASM2) — Parallax Forums

Working With CRC (Inline PASM2)

JonnyMacJonnyMac Posts: 9,102
edited 2023-02-12 20:21 in Propeller 2

It was cold in LA today (okay, colder that it should have been), so I stayed indoors and went down a rabbit hole (I will finish those PCB layouts tomorrow). While trying the new getcrc() function in Spin2 I found that it doesn't match my (and others) CRC-8 for 1-Wire, and I often use this in other places. For history, here's where I started:

pub crc8x(p_src, n) : crc | b

  repeat n
    b := byte[p_src++]
    repeat 8
      if (crc ^ b) & 1
        crc := (crc >> 1) ^ $8C
      else
        crc >>= 1
      b >>= 1

This implementation was by Micah Dowty. It's slow in Spin, so I use it in the P1 when speed is not an issue. Thankfully, Cam Thompson (RIP) provided a PASM version of it that is in my P1 1-Wire library, and I extracted once for a project that needed faster CRC calculations than Spin1 could provide (luckily, I had a spare cog).

For the, P2, that method can be run inline using the crcnib instruction. Over time it's become:

pub crc8_maxim(p_src, count) : crc | b

  org
                        rdfast    #0, p_src
.loop                   rfbyte    b
                        rev       b
                        setq      b
                        crcnib    crc, #$8C
                        crcnib    crc, #$8C
                        djnz      count, #.loop
  end

The other CRC that I use is 16-bit for MODBUS. This is the inline version:

pub crc16_modbus(p_src, count) : crc | b

  org
                        mov       crc, ##$FFFF
                        rdfast    #0, p_src
.loop                   rfbyte    b
                        rev       b
                        setq      b
                        crcnib    crc, ##$A001
                        crcnib    crc, ##$A001
                        djnz      count, #.loop
  end

The difference here is initial value and polynomial. This is the rub with Spin2's getcrc(): it guesses your CRC size based on the polynomial and always fills the initial result with 1s. Sadly, it doesn't work with MODBUS either.

This is the an inline version of getcrc() -- it matches the interpreter

pub spin2_getcrc(p_buf, poly, count) : crc | b

  org
                        encod     crc, poly                     ' preset result
                        bmask     crc
                        tjz       count, #.done                 ' exit if count = 0

                        rdfast    #0, p_buf
.loop                   rfbyte    b
                        shl       b, #24
                        setq      b
                        crcnib    crc, poly
                        crcnib    crc, poly
                        djnz      count, #.loop
.done
  end

There's a shl where I think there should be a rev; if you replace that and reflect the polynomial, it will work for MODBUS (but that's not what's in the interpreter).

In the end, I made this generic method that is a bit of a monster and works with all but three of the test cases I pulled down from https://crccalc.com/

pub get_crc(size, poly, startcrc, ri, rr, xr, p_src, count) : crc | b

'' Returns CRC of buffer at p_scr
'' -- size....... CRC type, e.g., 8, 16, 32
'' -- poly....... polynomial for crc calculation
'' -- startcrc... initial crc
'' -- ri......... !0 if input is reflected
'' -- rr......... !0 if result is reflected
'' -- xr......... xor with result
'' -- p_src...... pointer to buffer
'' -- count...... bytes in buffer

  org
                        rev       poly                          ' reflect poly for crcnib
                        rol       poly, size
                        mov       crc, startcrc                 ' get starting value
                        tjnz      rr, #$+3                      ' match result reflection
                        rev       crc
                        rol       crc, size
                        rdfast    #0, p_src                     ' setup buffer read
.loop                   rfbyte    b
                        tjnz      ri, #$+3                      ' input reflection
                        rev       b
                        rol       b, #8
                        rev       b                             ' crc work here
                        setq      b
                        crcnib    crc, poly
                        crcnib    crc, poly
                        djnz      count, #.loop                 ' done?
                        tjnz      rr, #$+3                      ' result reflection
                        rev       crc
                        rol       crc, size
                        xor       crc, xr
  end

The test program is attached. It works in Propeller Tool, FlexProp, and Spin Tools.

PS: I'm sure one of the clever PASM2 gurus will find ways to improve this. Please be kind and share.

Edit: Code as been updated per excellent suggestion from TonyB; new code attached to post #3.

Comments

  • TonyB_TonyB_ Posts: 2,178
    edited 2023-02-12 17:50

    In this case the following code

                    rev       b
                    rol       b, #8
                    rev       b    
    

    has the same result as

                    shl       b, #24
    

    I've never used P2 CRC's but I think you need shl b,#24 for non-reflected bits or rev b for reflected bits. If ri and rr were combined into a single 2-bit value say r then rczr r wcz would set c = r[1] & z = r[0] and you could use conditional instructions instead of tjnz x2.

    EDIT:
    added wcz

  • JonnyMacJonnyMac Posts: 9,102
    edited 2023-02-13 22:20

    Thanks for the cool tip, Tony!

    pub get_crc(size, poly, startcrc, reflect, xr, p_src, count) : crc | b
    
    '' Returns CRC of buffer at p_scr
    '' -- size....... CRC type, e.g., 8, 16, 32
    '' -- poly....... polynomial for crc calculation
    '' -- startcrc... initial crc
    '' -- reflect.... bit1 for input reflection, bit0 for output reflection
    '' -- xr......... xor with result
    '' -- p_src...... pointer to buffer
    '' -- count...... bytes in buffer
    
      org
                            mov       crc, startcrc                 ' get starting value
                            tjz       count, #.done
                            rczr      reflect               wcz     ' get reflection bits
            if_nz           rev       crc                           ' match output reflection
            if_nz           rol       crc, size
                            rev       poly                          ' reflect poly for crcnib
                            rol       poly, size
                            rdfast    #0, p_src                     ' setup buffer read
                            rep       @.crcend, count
                             rfbyte   b
            if_nc            shl      b, #24
            if_c             rev      b
                             setq     b
                             crcnib   crc, poly
                             crcnib   crc, poly
    .crcend
            if_nz           rev       crc
            if_nz           rol       crc, size
                            xor       crc, xr
    .done
      end
    

    EDIT: Updated -- again! -- per great tip from TonyB

  • roglohrogloh Posts: 5,786
    edited 2023-02-13 02:10

    Really nice work Jon. I'm sure a universal CRC routine like this will come in very handy for people.
    EDIT: also same to Chip who I just learned may have created the original...I've not been following the forums a lot lately.

  • JonnyMacJonnyMac Posts: 9,102
    edited 2023-02-13 17:14

    Truth be told, I got a lot of great feedback and help in this thread
    -- https://forums.parallax.com/discussion/171293/crc8

    When Chip releases a new version of PNut, the updated interpreter source is included, so I was able to learn from it (e.g., I had never used rdfast. While chatting with Chip about a week ago he mentioned wanting to create a general-purpose CRC function so I spent a chilly Saturday indoors experimenting. And then TonyB helped me improve on that. I appreciate the really great programmers in the forums willing to share small -- but often very powerful -- tips with the rest of us.

  • Jon,
    Of course you are one of them!

    I appreciate the really great programmers in the forums willing to share small -- but often very powerful -- tips with the rest of us

  • Jon, if code is in cog/LUT RAM then rep is faster alternative to djnz and could cut CRC loop time by 25%,
    from this

    .loop                   rfbyte    b
            if_nc           shl       b, #24
            if_c            rev       b
                            setq      b
                            crcnib    crc, poly
                            crcnib    crc, poly
                            djnz      count, #.loop                 ' done?
    

    to this

                            rep       @.crcend, count
                            rfbyte    b
            if_nc           shl       b, #24
            if_c            rev       b
                            setq      b
                            crcnib    crc, poly
                            crcnib    crc, poly
    .crcend
    

    Interrupts ignored during REP blocks and count not changed.

  • JonnyMacJonnyMac Posts: 9,102
    edited 2023-02-13 22:21

    Thanks, again, Tony! Updated post #3.

Sign In or Register to comment.