SDCC Part 3 of 4 : Using Maxam-style inlined assembly code in your C programs
In previous article, I presented you how to build your own C programs. The internal SDCC assembly syntax was also briefly shown : it differs from any standards known by Amstrad CPC users. This article covers the way to use Maxam-style syntax for assembly inlined code in your C programs.
Why using inlined assembly code in a C program ?
Even if SDCC is a C compiler, you unfortunately still need an assembly layer to the target system and C language. By example, C language does not contain these out (c), X instructions to drive the hardware. Additionally to that, there are many cases where you need to write more efficient assembly code than the one generated by SDCC.
SDCC2Pasmo : the conversion tool
SDCC does not know anything about Maxam-style syntax. As a consequence, development pipeline had to be updated to let it know about it. There were 2 possible choices : modify SDCC itself to generate .asm files using Maxam-style syntax, or converting its .asm output to Maxam-style syntax using an external custom tool. This second solution was finally kept, allowing future SDCC updates and also a less complicated install for the user. So I created SDCC2Pasmo, which is a command-line tool handling that conversion task. The tool can be downloaded here (also listed in Productions section).
SDCC2Pasmo usage
To convert an .asm file generated by SDCC to one compatible with Maxam-style, simply use the following command :
SDCC2Pasmo source.asm destination.asm
SDCC2Pasmo source.asm destination.asm
Presenting Pasmo
Maxam is a powerful Z80 assembler for the Amstrad CPC. Pasmo is its PC counter-part, being a Z80 cross-compiler for our nowadays platforms.
To compile assembly source-code with Pasmo, simply use the following command :
pasmo main.asm main.bin
To compile assembly source-code with Pasmo, simply use the following command :
pasmo main.asm main.bin
Workflow
So basically, we use SDCC to translate a C source file to an assembly one. We don't use SDCC anymore (as presented in Part 2) to generate the binary, but use Pasmo Z80 assembler instead.
Hello World !
When executed on Amstrad CPC, our example displays a Hello World ! message to the screen. Copy / paste this source in your favorite text editor and save the document as main.c file.
void DisplayChar( char c )
{
__asm
ld a, ( ix + 4 )
call &bb5a
__endasm;
}
main()
{
const char *textPtr = "Hello World !";
while( *textPtr != 0 )
{
DisplayChar( *textPtr );
textPtr++;
}
__asm
call &bb06
__endasm;
return 0;
}
Some details now about this source. Every content stored under __asm and __endasm; instructions will be ignored by the C compiler, but injected as is in generated .asm file. It makes use of well-known Amstrad CPC's DisplayText (&BB5A) and WaitKey (&BB06) firmware routines. Now the tricky part is ld a, ( ix + 4 ) instruction. Let's have a deeper look in generated SDCC code :
_DisplayChar:
push ix
ld ix,0
add ix,sp
;main.c:8: __endasm;
ld a, ( ix + 4 )
call &bb5a
_DisplayChar_00101:
pop ix
ret
The parameters sent to DisplayChar function are sent through the stack. The compiler automatically added the push ix / ld ix, 0 / add ix, sp instructions at the beginning, as also this pop ix instruction at the end. First param is always ix + 4 (16 bits value, even if the parameter is 8 bits only). Secondary param would be ix + 6, and so on. If the function does not have any input parameters, then these instructions are not added to the source.
Also take note that Maxam-style syntax is used here for inlined assembly code.
void DisplayChar( char c )
{
__asm
ld a, ( ix + 4 )
call &bb5a
__endasm;
}
main()
{
const char *textPtr = "Hello World !";
while( *textPtr != 0 )
{
DisplayChar( *textPtr );
textPtr++;
}
__asm
call &bb06
__endasm;
return 0;
}
Some details now about this source. Every content stored under __asm and __endasm; instructions will be ignored by the C compiler, but injected as is in generated .asm file. It makes use of well-known Amstrad CPC's DisplayText (&BB5A) and WaitKey (&BB06) firmware routines. Now the tricky part is ld a, ( ix + 4 ) instruction. Let's have a deeper look in generated SDCC code :
_DisplayChar:
push ix
ld ix,0
add ix,sp
;main.c:8: __endasm;
ld a, ( ix + 4 )
call &bb5a
_DisplayChar_00101:
pop ix
ret
The parameters sent to DisplayChar function are sent through the stack. The compiler automatically added the push ix / ld ix, 0 / add ix, sp instructions at the beginning, as also this pop ix instruction at the end. First param is always ix + 4 (16 bits value, even if the parameter is 8 bits only). Secondary param would be ix + 6, and so on. If the function does not have any input parameters, then these instructions are not added to the source.
Also take note that Maxam-style syntax is used here for inlined assembly code.
Program's startup
When compiled, our generated asm file does not contain any origin information (the Maxam's org &XXXX instruction). It's not possible to put this instruction inside __asm / __endasm; instruction because the final location in generated source code can't be known. The solution is to use a secondary .asm file which includes correct origin information and includes our freshly generated main.asm file :
org &4000
jp _main
include "main_maxam.asm"
Copy / paste this content as save it as main_entrypoint.asm file.
This file is the only one to be compiled by Pasmo Z80 assembler.
org &4000
jp _main
include "main_maxam.asm"
Copy / paste this content as save it as main_entrypoint.asm file.
This file is the only one to be compiled by Pasmo Z80 assembler.
Build script
To sum everything, this is the batch file content used to compile our Hello World example :
sdcc -mz80 -S --no-std-crt0 --vc main.c
SDCC2Pasmo main.asm main_maxam.asm
pasmo main_entrypoint.asm main.bin
As a result, main.bin is a binary file ready to be inserted in any DSK file.
Note that Tutorial Part 2 's sdcc usage command differs here : -S flag tells SDCC to generate the .asm file but it does not compile it !
Of course, using a MAKEFILE or other better solutions is also possible for your developments.
sdcc -mz80 -S --no-std-crt0 --vc main.c
SDCC2Pasmo main.asm main_maxam.asm
pasmo main_entrypoint.asm main.bin
As a result, main.bin is a binary file ready to be inserted in any DSK file.
Note that Tutorial Part 2 's sdcc usage command differs here : -S flag tells SDCC to generate the .asm file but it does not compile it !
Of course, using a MAKEFILE or other better solutions is also possible for your developments.
Use Code::Blocks IDE as source-code editor
Octoate created a nice template-project to be used with Code::Blocks IDE. Make sure to have a look at it here ! :)
Conclusion
This article is now over. It showed you how to use Maxam-style inlined assembly code with your C programs.
More information and details will be covered in the Part 4 of the tutorials.
More information and details will be covered in the Part 4 of the tutorials.