Starting from a small snippet of c-code this example shows for the MCS51 how to use inline assembly, access variables, a function parameter and an array in xdata memory. The example uses an MCS51 here but is easily adapted for other architectures. This is a buffer routine which should be optimized:
unsigned char __far __at(0x7f00) buf[0x100];
unsigned char head, tail; /* if interrupts are involved see
sectionaboutvolatile */
void to_buffer( unsigned char c )
{
if( head != (unsigned char)(tail-1) ) /* cast neededto avoid promotion to integer */
buf[ head++ ] = c; /* access to a 256 byte aligned array */
}
If the code snippet (assume it is saved in buffer.c) is compiled with SDCC then a corresponding buffer.asm file is generated. We define a new function to_buffer_asm() in file buffer.c in which we cut and paste the generated code, removing unwanted comments and some ':'. Then add ”__asm” and ”__endasm;”typeset@protect @@footnote SF@gobble@opt Note, that the single underscore form (_asm and _endasm) are not C99 compatible, and for C99 compatibility, the double-underscore form (__asm and __endasm) has to be used. The latter is also used in the library functions. to the beginning and the end of the function body:
/* With a cut and paste from the .asm file, we have something to start with.
The function is not yet OK! (registers aren't saved) */
void to_buffer_asm( unsigned char c )
{
__asm
mov r2,dpl
;buffer.c if( head != (unsigned char)(tail-1) ) /* cast neededto avoid promotion to integer */
mov a,_tail
dec a
mov r3,a
mov a,_head
cjne a,ar3,00106$
ret
00106$:
;buffer.c buf[ head++ ] = c; /* access to a 256 byte aligned array */
mov r3,_head
inc _head
mov dpl,r3
mov dph,#(_buf » 8)
mov a,r2
movx @dptr,a
00103$:
ret
__endasm;
}
The new file buffer.c should compile with only one warning about the unreferenced function argument 'c'. Now we hand-optimize the assembly code and insert an #define USE_ASSEMBLY (1) and finally have:
unsigned char __far __at(0x7f00) buf[0x100];
unsigned char head, tail;
#define USE_ASSEMBLY (1)
#if !USE_ASSEMBLY
void to_buffer( unsigned char c )
{
if( head != (unsigned char)(tail-1) )
buf[ head++ ] = c;
}
#else
void to_buffer( unsigned char c )
{
c; // to avoid warning: unreferenced function argument
__asm
; save used registers here.
; If we were still using r2,r3 we would have to push them here.
; if( head != (unsigned char)(tail-1) )
mov a,_tail
dec a
xrl a,_head
; we could do an ANL a,#0x0f here to use a smaller buffer (see below)
jz t_b_end$
;
; buf[ head++ ] = c;
mov a,dpl ; dpl holds lower byte of function argument
mov dpl,_head ; buf is 0x100 byte aligned so head can be used directly
mov dph,#(_buf»8)
movx @dptr,a
inc _head
; we could do an ANL _head,#0x0f here to use a smaller buffer (see above)
t_b_end$:
; restore used registers here
__endasm;
}
#endif
The inline assembler code can contain any valid code understood by the assembler, this includes any assembler directives and comment lines. The assembler does not like some characters like ':' or ”' in comments. You'll find an 100+ pages assembler manual in sdcc/sdas/doc/asmlnk.txt or online at http://svn.code.sf.net/p/sdcc/code/trunk/sdcc/sdas/doc/asmlnk.txt.
The compiler does not do any validation of the code within the __asm ... __endasm; keyword pair. Specifically it will not know which registers are used and thus register pushing/popping has to be done manually.
It is required that each assembly instruction be placed on a separate line. This is also recommended for labels (as the example shows). This is especially important to note when the inline assembler is placed in a C preprocessor macro as the preprocessor will normally put all replacing code on a single line. Only when the macro has each assembly instruction on a single line that ends with a line continuation character will it be placed as separate lines in the resulting .asm file.
#define DELAY \
__asm \
nop \
nop \
__endasm
When the --peep-asm command line option is used, the inline assembler code will be passed through the peephole optimizer. There are only a few (if any) cases where this option makes sense, it might cause some unexpected changes in the inline assembler code. Please go through the peephole optimizer rules defined in file peeph.def before using this option.