I want to write an assembly version of this function:
uint16_t intel16_asm( uint16_t value);
Looking at the assembly generated for a C version of that function, it would appear that return address is passed in H:X, the parameter is on the stack (SP+1 holds MSB, SP+2 holds LSB) and the return is expected in H:X. So, I came up with the following assembly to swap the byte order:
PSHX ; push return addressPSHH LDHX 4, SP ; load LSB of parameter to H, junk to XLDX 3, SP ; load MSB of parameter to XRTS ; return
So what syntax should I use to create this function? Can I mark it as inline and have the compiler inline the assembly so it loads a 16-bit value into H:X and then does the PSHH PSHX PULH PULX sequence to swap the byte order?
I tried doing:
uint16_t intel16_asm( uint16_t value){ asm { PSHH PSHX PULH PULX RTS }}
But then I get a warning because the function doesn't return. Is it even safe to use RTS in the assembly? If I take the RTS out, how do I indicate to the compiler that the return value is already loaded in H:X? How do I tell the compiler not to push the return address for me?
If I want to write the 32-bit version of this function, I assume a 32-bit parameter is passed on the stack in a similar fashion? How is a 32-bit return value passed out?
Finally, any recommended tutorials or references for learning HC08 assembly? I did HC11 over 18 years ago, and don't remember anything about it. I've been doing a lot of Rabbit assembly (originally based on Z80 instruction set) over the past 10 years, and so far the HC08 syntax has been very foreign to me.
-Tom
(HC08 with CodeWarrior 5.9.0)
Solved! Go to Solution.
Well, I finally figured out a workable solution, but it's really ugly. Surely there's an easier way, maybe with just having some assembly outside of the C function?
#pragma MESSAGE DISABLE C1404 // Return expected#pragma NO_ENTRY#pragma NO_EXIT#pragma NO_FRAME#pragma NO_RETURNuint16_t intel16_asm( uint16_t value){ asm { PSHH PSHX PULH PULX RTS }}#pragma MESSAGE DEFAULT C1404
My first assumption had been right (parameter 1 in H:X), but I second guessed myself and edited the message with the incorrect assumption that H:X had the return address (which wouldn't really make sense).
My confusion came from looking at the disassembly of another function without parameters. It pushed H:X onto the stack right away, but maybe that was to make room for a return value...
Well, I finally figured out a workable solution, but it's really ugly. Surely there's an easier way, maybe with just having some assembly outside of the C function?
#pragma MESSAGE DISABLE C1404 // Return expected#pragma NO_ENTRY#pragma NO_EXIT#pragma NO_FRAME#pragma NO_RETURNuint16_t intel16_asm( uint16_t value){ asm { PSHH PSHX PULH PULX RTS }}#pragma MESSAGE DEFAULT C1404
My first assumption had been right (parameter 1 in H:X), but I second guessed myself and edited the message with the incorrect assumption that H:X had the return address (which wouldn't really make sense).
My confusion came from looking at the disassembly of another function without parameters. It pushed H:X onto the stack right away, but maybe that was to make room for a return value...
Hello,
For the 16-bit case, the following appears to generate efficient code.
uint16_t intel16( uint16_t value){ (void)value; // Avoids unused parameter compiler warning __asm { pulh pulx pshh pshx } return value;}
Since the entry to the function already pushes the argument onto the stack from H:X, the idea is to pull off the stack, and replace to the stack in reverse order. An interesting observation is that the compiler actually optimises away the function entry pushes, and the subsequent inline pulls, leaving the following resultant code for the function, that achieves the required result.
pshh pshx ldhx 1,sp ais #2 rts
Regards,
Mac
Has been a while since I last did S08 asm programming, so just some hints.
Note that the calling convention is different for the S08 and for the orginal HC08. As the S08 support more addressing modes with LDHX/STHX, one 16 bit argument is passed in H/X. for the HC08 A and X is used.
Long returns (anything else > 2 bytes) are done with an additional in pointer argument to a buffer.
Check the compiler manual, should have the basic calling convention described in there.
I think using the pragmas is the best way, well the RTS could be done by the compiler, does not really matter.
An alternative would be a plain assembly implementation of the function.
Assembly code outside of function is not allowed, the compiler is not using the assembler but directly emits object files.
Daniel
Thanks for the info. I am using an S08, not an HC08. Here's what I came up with for a 32-bit swap:
#pragma NO_ENTRY#pragma NO_EXIT#pragma NO_FRAME#pragma NO_RETURNuint32_t swap32( uint32_t value){ /* Stack offset: 1: LSB of return address 2: MSB of return address 3: byte0 (LSB) param1 4: byte1 of param1 5: byte2 of param1 6: byte3 of param1 7: LSB of address of return value 8: MSB of address of return value */ asm { LDHX 7, SP ; H:X is index to return value LDA 3, SP ; load byte 0 of parameter STA 3, X ; save as byte 3 of return LDA 4, SP ; load byte 1 of parameter STA 2, X ; save as byte 2 of return LDA 5, SP ; load byte 2 of parameter STA 1, X ; save as byte 1 of return LDA 6, SP ; load byte 3 of parameter STA 0, X ; save as byte 0 of return RTS }}
Looks reasonable.
In the comment, the MSB and LSB's are in the wrong order, S08's are big endian, therefore the MSB is at the lower address.