Jump to content
 
  • 0
ric

Fast C parity calculations on PIC16F

Question

This is my attempt to convert the blazingly fast assembler routine for calculating parity into a C function.

The original comes from here: https://www.microchip.com/forums/m4762.aspx

Unfortunately Microchip have killed off the board where the original discussion took place at http://asp.microchip.com/webboard/wbpx.dll/~DevTools/read?21443,5

#include <xc.h>

//returns 0x00 (even parity) or 0x80 (odd parity)
unsigned char parity(volatile unsigned char dat)    //("volatile" is required because no C code reads the parameter)
{
    asm("swapf parity@dat,w");    //assume correct bank is still selected
    asm("xorwf parity@dat,w");    //W has 8 bits reduced to 4 with same parity
    asm("addlw 41h");   // bit 1 becomes B0^B1 and bit 7 becomes B6^B7
    asm("iorlw 7Ch");   // for carry propagation from bit 1 to bit 7
    asm("addlw 2");     // Done! the parity bit is bit 7 of W
    asm("andlw 80h");   // set NZ if odd parity, and leave 00 or 80 in W
    asm("return");
    return 1;           //dummy instruction to defeat "no return value" error
}

void main(void) {
    unsigned char idx=0;
    while(1)
    {
        PORTA = parity(idx);
        idx++;
    }
}

I'm not sure if there's a cleaner way to suppress the "no return value" error, without generating extra code.

Share this post


Link to post
Share on other sites

9 answers to this question

Recommended Posts

  • 0

A suggested C macro is almost as good, but adds an extra temporary storage location for the first calculation

#define PARITY(b)   ((((((b)^(((b)<<4)|((b)>>4)))+0x41)|0x7C)+2)&0x80)

It also generates a "warning: (752) conversion to shorter data type" on the macro invocation.

Share this post


Link to post
Share on other sites
  • 0

What happens when you do "return WREG" as the last line? I think in PRO mode on XC8 that may work and you can remove the return asm line. I can check tomorrow if this works, you can probably check it right now ūüôā

Share this post


Link to post
Share on other sites
  • 0
10 minutes ago, Orunmila said:

What happens when you do "return WREG" as the last line? I think in PRO mode on XC8 that may work and you can remove the return asm line. I can check tomorrow if this works, you can probably check it right now ūüôā

That was what I did first, and was horrified by the code generated by XC8 2.0 in free mode.

That was my attempt to force it to do it efficiently.

XC8 v.1.34 in Pro  mode only has one pointless instruction:

   309                           ;main.c: 41: return WREG;
   310  07FE  0809               	movf	9,w	;volatile
   311  07FF  0008               	return

Std and Free mode have an extra pointless instruction:

   320  07FD  0020               	movlb	0	; select bank0
   321  07FE  0809               	movf	9,w	;volatile
   322  07FF  0008               	return

and XC8 2.0 in C90 Free mode (Opt 0) is a bit ordinary:

  1218  07D4                     ;main.c: 41: return WREG;
  1219                           	movlb 0	; select bank0
  1220  07D4  0020               	movf	(9),w	;volatile
  1221  07D5  0809               	goto	l9
  1222  07D6  2FD7               	
  1223                           l594:	
  1224  07D7                     	line	42
  1225                           	
  1226                           l9:	
  1227  07D7                     	return

C90 Free / Opt 1 is same as v1.x in free mode

   541                           ;main.c: 41: return WREG;
   542  07D9  0020               	movlb	0	; select bank0
   543  07DA  0809               	movf	9,w	;volatile
   544  07DB  0008               	return

C99 mode generated the same code.

(And that macro generates horrible code if you are not in Pro mode...)

  • I Agree 1

Share this post


Link to post
Share on other sites
  • 0

Sadly, free mode is embarrassing and pro mode is expensive.  It has been for a long time.

  • I Agree 1

Share this post


Link to post
Share on other sites
  • 0

I have been playing for a while with coding functions in ASM which can be called from C to cater specifically to cases like these.

One way to get the code to be exactly what you need it to be like this case here is to add a .S file to your project and code it like this. This will be accessible as a C function of type 4217 (passes in 1 byte in W, returns 1 byte in W)

I made a small change there to what you had because you assumed the parameter was NOT passed in W, while with XC8 this is the normal way it will be done and if it is passed in W the code seems like it would fail. I just declared a local var and moved w into it as you assumed was happening during the call.

If you want to save that extra instruction and 1 byte of ram you can remove the parameter altogether, and code the ASM code to read it from the source symbol.

#include <xc.inc>
GLOBAL _calcParity        ; make _calcParity globally accessible
SIGNAT _calcParity,4217   ; tell the linker how it should be called

GLOBAL    calcParity@tmp
    
PSECT cstackCOMMON,class=COMMON,delta=1,space=1
calcParity@tmp ds 1
    
; everything following will be placed into the mytext psect
PSECT mytext,global,class=CODE,delta=2

; our routine to calculate the parity
_calcParity:

; W is loaded by the calling function;
movwf calcParity@tmp
swapf calcParity@tmp,w   ; assume correct bank is still selected
xorwf calcParity@tmp,w   ; W has 8 bits reduced to 4 with same parity
addlw 41h                ; bit 1 becomes B0^B1 and bit 7 becomes B6^B7
iorlw 7Ch                ; for carry propagation from bit 1 to bit 7
addlw 2                  ; Done! the parity bit is bit 7 of W
andlw 80h                ; set NZ if odd parity, and leave 00 or 80 in W

; the result is already in the required location (W)so we can ; just return immediately
return

 

You can then call that from C like so :

#include <xc.h>
#include <stdint.h>

uint8_t calcParity(uint8_t dat);

void main(void) {
    volatile uint8_t c = 0;
    
    c = calcParity(0x01);
    c = calcParity(0x11);
    c = calcParity(0x03);
    c = calcParity(0x13);
}


Not sure if this helps you.

We have started a conversation with the compiler team to see what they think about optimizing out movf w,w since that never makes sense (although it is a trick used to clear status flags in some cases...). Lots of people still on Vacation until Monday, so we will have to be patient for their input.


 

Share this post


Link to post
Share on other sites
  • 0
1 hour ago, Orunmila said:

One way to get the code to be exactly what you need it to be like this case here is to add a .S file to your project and code it like this. This will be accessible as a C function of type 4217 (passes in 1 byte in W, returns 1 byte in W)

I made a small change there to what you had because you assumed the parameter was NOT passed in W, while with XC8 this is the normal way it will be done and if it is passed in W the code seems like it would fail. I just declared a local var and moved w into it as you assumed was happening during the call.

Very nice.

You're right about the bug though. I think I got conned by C99 mode with no optimisation. Normally the parameter is just in W, so you do need to create a temporary variable and save it as you did.

Is your tmp variable essentially static? i.e. are other functions at the same level able to overlay that location?

 

Quote

If you want to save that extra instruction and 1 byte of ram you can remove the parameter altogether, and code the ASM code to read it from the source symbol.

Now THIS sounds interesting. How would you do that?

This function does not alter the scratch location, it just needs to be able to read it.

Share this post


Link to post
Share on other sites
  • 0

I am still learning how the linker interprets things. This is using the compiled stack and the linker should have all the information it needs to re-use the location but I have no idea if it will. 

EDIT: Actually the more I look at that the more I think it will not re-use the memory :(. It is just saying reserve 1 byte of common ram, not associated with the function call.

I have been playing with proxy functions, and I was hoping that once they fix inlining in the compiler that this would give a good path to making ASM functions with a proxy wrapper like this.

This way the C function gets all the bells and whistles that C provides in terms of stack management, and you can use SW or compiled stack if you please, or even just make this function reentrant, and it all still works. It gets really complicated when you inline the c function and use it more than once, but if you could coax the linker into inlining the ASM function since it is called only once ever, this would be a really neat solution. 

As things stand now you end up with an extra call and return which is really 4 instuctions penalty, so if you are doing this for a speed optimization that sucks, if you are doing it to access some fancy ASM code then it works nicely.

uint8_t asm_calcParity();

uint8_t calcParity(uint16_t c)
{
    volatile uint16_t  tmp;  // This generates no code but reserves the temp stack space
    tmp = c;                 // This simply does a MOVWF tmp
    
    return asm_calcParity(); // And since this is returning what another function returned in W it should just generate a CALL and RETURN
}

Then the ASM just becomes this:

#include <xc.inc>
GLOBAL _asm_calcParity        ; make _calcParity globally accessible
SIGNAT _asm_calcParity,4217   ; tell the linker how it should be called
   
GLOBAL calcParity@tmp
    
; everything following will be placed into the mytext psect
PSECT mytext,global,class=CODE,delta=2

; our routine to calculate the parity
_asm_calcParity:

; W is loaded by the calling function;
swapf calcParity@tmp,w   ; assume correct bank is still selected
xorwf calcParity@tmp,w   ; W has 8 bits reduced to 4 with same parity
addlw 41h                ; bit 1 becomes B0^B1 and bit 7 becomes B6^B7
iorlw 7Ch                ; for carry propagation from bit 1 to bit 7
addlw 2                  ; Done! the parity bit is bit 7 of W
andlw 80h                ; set NZ if odd parity, and leave 00 or 80 in W

; the result is already in the required location (W)so we can ; just return immediately
return

 

 

Share this post


Link to post
Share on other sites
  • 0

I just had a naughty thought.

If you restrict usage to enhanced devices, presumably our routine is allowed to trash WREG, STATUS and BSR.

What if we just used BSR as our temporary scratch register? It's a core register, so doesn't need banking to access (obviously!), and is automatically preserved when interrupts are serviced. The remaining four instructions are all immediate, so not affected by BSR.

Edit: Scrub that. BSR is only 5 or 6 bits, never 8, so can't be used for our purpose which requires all 8 bits.

Share this post


Link to post
Share on other sites
  • 0

Be careful, you have to tell the compiler if you are going to affect BSR specifically, it tracks the banks and will optimize to minimize bank switches but this can get you into real trouble!

What I like about the proxy/trampoline type approach I posted before is that it forces the banks to be correct because of the compiler managed local variables.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


 


  • Popular Contributors

  • Similar Content

    • By Orunmila
      error: variable has incomplete type 'void'
      If you are getting this error message trying to compile code that used to work or from some example code you found somewhere it is very likely because of the changes in the XC8 interrupt syntax introduced with V2.0 of XC8.
      Specifically I am getting this today for my interrupt service routine definition. I used to use the age-old way of doing this in XC8 as follows:
      // Old way to do this void interrupt myISR(void) { // Old way Interrupt code here } After the changes to the XC8 compiler which were mostly motivated to get better C standard compliance, particularly with C99, the syntax for that function should now use the commonly adopted concept of function declaration-specifier attributes, which traditionally start with 2 underscores and contains either a list of parameters in brackets or have empty brackets if no parameters are present. 
      // New and improved and C99 compliant way to specify an interrupt service routine in XC8 void __interrupt() myISR(void) { // New and improved interrupt code here } This syntax is now also consistent between XC8, XC16 and XC32
      Please see this post for more information on how to either work around this or change to the new syntax.
      https://www.microforum.cc/topic/5-i-used-to-use-to-locate-variables-but-since-xc8-20-this-is-no-longer-working/
    • By Orunmila
      I just downloaded XC32 V2.15, I was using V2.10 before. I find that some of my projects no longer compile. On my first check I noticed that the problems seem to occur when inline functions are used and the same header where the inline implementation is done is included in more than one compilation unit?
      Has any of you seen similar issues?
      I will investigate further and post here if I arrive at an answer.
       
      UPDATE: Ok, I managed to make a small test project to replicate the problem. I am attaching it here. 
      TestInlineXC32_2.15.zip
       
      Next I am going to test this on some other compilers to see what the deal is. I have confirmed that with that project when you switch it to V2.10 or older it all compiles just fine, but if you use V2.15 it failes to link with the following error:
      "/Applications/microchip/xc32/v2.15/bin/xc32-gcc"   -mprocessor=32MZ2048EFM100  -o dist/default/production/TestInlineXC32_2.15.X.production.elf build/default/production/main.o build/default/production/otherFile.o          -DXPRJ_default=default  -legacy-libc    -Wl,--defsym=__MPLAB_BUILD=1,--no-code-in-dinit,--no-dinit-in-serial-mem,-Map="dist/default/production/TestInlineXC32_2.15.X.production.map",--memorysummary,dist/default/production/memoryfile.xml
      nbproject/Makefile-default.mk:151: recipe for target 'dist/default/production/TestInlineXC32_2.15.X.production.hex' failed
      make[2]: Leaving directory '/Users/ejacobus/MPLABXProjects/TestInlineXC32_2.15.X'
      nbproject/Makefile-default.mk:90: recipe for target '.build-conf' failed
      make[1]: Leaving directory '/Users/ejacobus/MPLABXProjects/TestInlineXC32_2.15.X'
      nbproject/Makefile-impl.mk:39: recipe for target '.build-impl' failed
      build/default/production/otherFile.o: In function `myInlineFunction':
      /Users/ejacobus/MPLABXProjects/TestInlineXC32_2.15.X/inlinedheader.h:6: multiple definition of `myInlineFunction'
      build/default/production/main.o:/Users/ejacobus/MPLABXProjects/TestInlineXC32_2.15.X/inlinedheader.h:6: first defined here
      /Applications/microchip/xc32/v2.15/bin/bin/gcc/pic32mx/4.8.3/../../../../bin/pic32m-ld: Link terminated due to previous error(s).
      collect2: error: ld returned 255 exit status
      make[2]: *** [dist/default/production/TestInlineXC32_2.15.X.production.hex] Error 255
      make[1]: *** [.build-conf] Error 2
      make: *** [.build-impl] Error 2
      BUILD FAILED (exit value 2, total time: 680ms)
       
       
       
    • By Orunmila
      If you have purchased a "MPLAB(R) Xpress PIC18F47K40 Evaluation Board" from Microchip (part number DM182027) and you are running into difficulty because the board is behaving strangely it is most likely caused by a silicon errata on this device!
      The errata can be downloaded here: http://ww1.microchip.com/downloads/en/DeviceDoc/PIC18F27-47K40-Silicon-Errata-and-Data-Sheet-Clarification-80000713E.pdf
      The relevant section of the Errata is shown at the end.
      What is happening is that the compiler is using a TBLRD instruction somewhere and this instruction is not behaving as expected due to a silicon bug in REV A2 of the PIC18F47K40, causing the read to fail and the program to malfunction. Typically this happens as part of the C initialization code generated by the XC8 compiler, and since the compiler is optimizing, changing the code may cause the problem to temporarily disappear because you have few enough global variables that a table read is no longer the fastest way to initialize the memory segment for variables with static linkage.
      The XC8 compiler can avoid generating the sequence which will cause the failure if you tell it in the linker settings to implement the workaround for this Errata. This is done by adding +NVMREG to the setting as follows. Note that this is under the section "XC8 Linker" and the Option Category "Additional Options".
       

       
      This is the relevant section of the Errata.

       
       
    • By KM1
      Hello,
      I have run into a strange (for me) issue with the rtcounter module as provided by MCC and shown by the example program in the blog area of this site. I am using MPLabX and XC8, both up-to-date versions and I am trying the example on a pic18F47k40 xpress board. The issue is I set an output (RA4) in main after initializing the pic and then enter the while(1) loop where the rtcount_callNextCallback(); is called. The output now is turning on and off. The off duration is typically ~60u seconds, and on time can be varied with changes to the timer interrupt settings (I use a 1mS timer0 in 16 bit mode) and the number sent to the callback. Approximately every 3mS, the off time increases such that that cycle (only) is approx 50% duty cycle.
      Turning the output on or off in the callback routine has no effect - I initially wanted to toggle on and off at a speed visible to the eye. Commenting out the callback in the main loop stops the output from going low.
      I have checked errata and used the data sheet to confirm register settings, I tried moving the current-limited LED from RA4 to RA2, and I have studied the example program looking for obvious differences that may cause this behaviour.
      I would appreciate thoughts and/or suggestions.
      Keith
         
    • By Orunmila
      I have a bunch of old projects using the @ method to hard locate variables at a specific location in data memory, but since I upgraded to XC8 2.0 none of these are working any more!
      How do I do this with the latest compiler? I am using XC8 v2.xx.
      I also saw this error at my interrupt service routine and I suspect that this is the same problem.
      My interrupt code looks like this 
      void interrupt my_isr(void) { ... my code here } error: variable has incomplete type 'void'
    • By Orunmila
      I think this is probably the most common question we get from beginners. Everybody gets confused with LATx and PORTx and run into Read-Modify-Write issues, so how does this really work?
       
×
×
  • Create New...