When writing code to be compiled with SDCC targetting Z80, assembler code fragments can be inserted in the C functions by enclosing them between the __asm
and __endasm;
tags:
void DoNotDisturb()
{
__asm
di
__endasm;
DoSomething();
__asm
ei
halt
__endasm;
}
SDCC 3.2 introduced the __asm__
keyword, that allows to specify the assembler code inside a string:
void DoNotDisturb()
{
__asm__ ("di");
DoSomething();
__asm__ ("ei\nhalt");
}
Adding __naked
to the function definition will cause the compiler to not generate the ret
statement at the end of the function, you will usually use it when the entire body of the function is written in assembler:
void TerminateProgram() __naked
{
__asm
ld c,#0
jp #5
__endasm;
}
Assembler code must preserve the value of IX, all other registers can be used freely.
The return value of a C function is passed to the caller as follows:
- Functions that return
char
: in theL
register - Functions that return
int
or a pointer: in theHL
registers - Functions that return
long
: in theDEHL
registers
char GetMagicNumber() __naked
{
__asm
ld l,#34
ret
__endasm;
}
int GetMagicYear() __naked
{
__asm
ld hl,#1987
ret
__endasm;
}
long GetReallyStrongPassword() __naked
{
__asm
ld de,#0x1234
ld hl,#0x5678
ret
__endasm;
}
Parameters for the function are pushed to the stack before the function is called. They are pushed from right to left, so the leftmost parameter is the first one found when going up in the stack:
char SumTwoChars(char x, char y) __naked
{
__asm
ld iy,#2
add iy,sp ;Bypass the return address of the function
ld l,(iy) ;x
ld a,1(iy) ;y
add l
ld l,a ;return value
ret
__endasm;
}
int SumCharAndInt(char x, int y) __naked
{
__asm
ld iy,#2
add iy,sp
ld e,(iy) ;x
ld d,#0
ld l,1(iy) ;y (low)
ld h,2(iy) ;y (high)
add hl,de ;return value
ret
__endasm;
}
long SumCharIntAndLong(char x, int y, long z) __naked
{
__asm
ld iy,#2
add iy,sp
ld c,(iy) ;x
ld b,#0
ld l,1(iy) ;y (low)
ld h,2(iy) ;y (high)
add hl,bc ;x+y
ld a,l
add 3(iy) ;z (lower)
ld l,a
ld a,h
adc 4(iy)
ld h,a
ld a,#0
adc 5(iy)
ld e,a
ld a,#0
adc 6(iy) ;z (higher)
ld d,a
ret ;return value = DEHL
__endasm;
}
It is possible to call other C functions from assembler code. Just push the parameters that the function expects, call the function assembler name (it's the C name prepended with _
), pop the parameters to restore the stack state, and act on the return value as appropriate:
int SumTwo(int x, int y)
{
return x+y;
}
int SumThree(int x, int y, int z) __naked
{
__asm
ld iy,#2
add iy,sp
ld l,2(iy)
ld h,3(iy)
push hl ;y for SumTwo
ld l,(iy)
ld h,1(iy)
push hl ;x for SumTwo
call _SumTwo ;Return value in HL
pop af ;x
pop af ;y
ld iy,#2
add iy,sp
ld e,4(iy)
ld d,5(iy) ;z
add hl,de
ret
__endasm;
}
It is possible to define pure assembler functions intended to be called exclusively from assembler code. In this case the standard parameter passing rules can be overriden:
//DO NOT call this function from C code!
int SumHLDE() __naked
{
__asm
add hl,de
ret
__endasm;
}
int SumThree(int x, int y, int z) __naked
{
__asm
ld iy,#2
add iy,sp
ld l,2(iy)
ld h,3(iy)
ld e,(iy)
ld d,1(iy)
call _SumHLDE
ld e,4(iy)
ld d,5(iy) ;z
add hl,de
ret
__endasm;
}
The Z80 assembler supplied with SDCC uses a pretty much standard syntax for the assembler source code except for the following:
- Decimal numeric constants must be preceded with
#
- Hexadecimal numeric constants must be preceded with
#0x
- The syntax for offsets when using index registers is
n(ix)
, where in other assemblers it's usually(ix+n)