This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

C51 tip - optimising MAKEDWORD()

I often use MAKEWORD and MAKEDWORD macros, which are defined to construct longer types from multiple shorter ones. e.g. -

#define MAKEWORD( h, l ) \ 
  ( ( ( WORD )( h ) << 8 ) | ( BYTE )( l ) )

which constructs a word from two bytes.

When used with constants, the calculations are all carried out at compile time and their efficiency doesn't matter. When used with data variables, code is generated the compiler's optimisation comes into play. The 8 bit shifts can be resolved to simple mov operations.

Keil does a good job optimising the MAKEWORD macro above, but does a lot less well with a similar macro to construct an unsigned long from two words or four bytes.

The following function works much better, by doing nothing! The trick is to define the word parameters so that they are passed in the same registers as a ULONG is returned, in the correct byte order. The function can the simply return, since the return value is the parameters.

This requires that the low word is passed as the first parameter, which means it is loaded to [R6|R7]. The high word is passed as the second parameter, in [R4|R5]. A ULONG is returned in [R4|R5|R6|R7], so all is as required on entry.

The line -

$reguse _make_dword ()

is important. It informs the compiler that no registers are disturbed. Without this, the calling function may generate extra code to preserve registers.

Code saved depends on too many variables to give an exact figure, but I get back around 20 bytes for a single use within a 'C' module. Your mileage may vary.

The code should also be much faster, although I haven't tested this.

A51?MAKE_DWORD segment code
rseg A51?MAKE_DWORD

;/ DWORD make_dword( WORD lo, WORD hi );
;/____________________________________________________________________________
$reguse _make_dword ()
public _make_dword
_make_dword:
                ret

Parents
  • I like the function cast idea!

    The hardware I'm working with uses bank switching, but has no common XDATA memory - each bank has separate physical XDATA memory. This means that all inter-bank calls must use register parameters only. So I have a lot of code which combines parameters into a longer type.

    Example - some of the following functions's parameters will be copied to XDATA.

    extern void rectangle( BYTE x0, BYTE y0, BYTE x1, BYTE y1 );
    

    But this is ok, since DWORD will be passed in regs.

    extern void i_rectangle( DWORD x0_y0_x1_y1 );
    
    #define rectangle( x0, y0, x1, y1 ) \ 
      i_rectangle( make_dword( MAKEWORD( x0, y0 ), MAKEWORD( x1, y1 ) ) )
    

    With the union implementation I had something like -

    extern void i_rectangle( DWORD x0_y0_x1_y1 );
    
    #define rectangle( x0, y0, x1, y1 )    \ 
    {                                      \ 
      union                                \ 
      {                                    \ 
         struct                            \ 
         {                                 \ 
           BYTE b3;                        \ 
           BYTE b2;                        \ 
           BYTE b1;                        \ 
           BYTE b0;                        \ 
         }                                 \ 
         as_byte;                          \ 
         DWORD as_dword;                   \ 
      }                                    \ 
      params;                              \ 
                                           \ 
      params.as_byte.b3 = x0;              \ 
      params.as_byte.b2 = y0;              \ 
      params.as_byte.b1 = x1;              \ 
      params.as_byte.b0 = y1;              \ 
                                           \ 
      i_rectangle( params.as_dword );      \ 
    }
    

    This resulted in less code than my original MAKEDWORD() macro. But the make_dword "function" is even more efficient.

Reply
  • I like the function cast idea!

    The hardware I'm working with uses bank switching, but has no common XDATA memory - each bank has separate physical XDATA memory. This means that all inter-bank calls must use register parameters only. So I have a lot of code which combines parameters into a longer type.

    Example - some of the following functions's parameters will be copied to XDATA.

    extern void rectangle( BYTE x0, BYTE y0, BYTE x1, BYTE y1 );
    

    But this is ok, since DWORD will be passed in regs.

    extern void i_rectangle( DWORD x0_y0_x1_y1 );
    
    #define rectangle( x0, y0, x1, y1 ) \ 
      i_rectangle( make_dword( MAKEWORD( x0, y0 ), MAKEWORD( x1, y1 ) ) )
    

    With the union implementation I had something like -

    extern void i_rectangle( DWORD x0_y0_x1_y1 );
    
    #define rectangle( x0, y0, x1, y1 )    \ 
    {                                      \ 
      union                                \ 
      {                                    \ 
         struct                            \ 
         {                                 \ 
           BYTE b3;                        \ 
           BYTE b2;                        \ 
           BYTE b1;                        \ 
           BYTE b0;                        \ 
         }                                 \ 
         as_byte;                          \ 
         DWORD as_dword;                   \ 
      }                                    \ 
      params;                              \ 
                                           \ 
      params.as_byte.b3 = x0;              \ 
      params.as_byte.b2 = y0;              \ 
      params.as_byte.b1 = x1;              \ 
      params.as_byte.b0 = y1;              \ 
                                           \ 
      i_rectangle( params.as_dword );      \ 
    }
    

    This resulted in less code than my original MAKEDWORD() macro. But the make_dword "function" is even more efficient.

Children