3.11.2 A Step by Step Introduction
Keywords: sdccman

##
3.11.2 A Step by Step Introduction

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
> section 3.8.1.1 about volatile */
> void to_buffer( unsigned char c )
> {
> if( head != (unsigned char)(tail-1) ) /* cast needed to 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;”3.5 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 needed to 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.