128k Programming basics using Z88DK and the SCCZ80 compiler Lesson 3

Placing Assembly Routines in Sections

This is the third lesson of programming the 128k ZX Spectrum. Today, I’m going to write about placing simple assembly routines in a specific memory space (SECTION) and calling it from C. This lesson is NOT about programming in assembly, I would quite possibly be the worst person to do so. I never really learned assembler and can sometimes muddle through simple stuff, many resources teach assembly language.

What I do want to show is a simple way to call simple assembly routines and have it be placed in a memory SECTION, whether that be in RAMLOW, RAMMAIN or BANK0 (home RAM).

If you missed out on lesson 2, it’s available at: https://zxspectrumcoding.wordpress.com/2021/08/07/128k-programming-basics-using-z88dk-and-the-sccz80-compiler-lesson-2-part-1/ .

First, let’s download the project files, they are available at: https://github.com/andydansby/ZX_Spectrum_128k_programming_lesson3 .

The version of Z88DK that I am using is 1.99c. We are going to be using the SCCZ80 and the new library. The directory that I am using for my installation of Z88DK is C:\z88dk199c and the directory that I am using for my example is C:\z88dk199c\games\128k_programming_2. If you use a different drive or directory, you will have to adjust the batch files accordingly. All of the text will be written as if you are using the same directories as I am.

So, let’s dive in.

There are not going to be a lot of changes within the code as we are really just expanding to what we already have programmed. What we are going to add is a flashing border routine. There is a built-in border routine within Z88dk, and we will use that, but we are also going to use an assembly routine to place it within a particular memory SECTION.

I chose a simple border flashing routine simply because I do not want to focus on assembly itself, rather its placement in a memory section. I also want to call attention to the EXTERN for assembly code as well as __FASTCALL__ keyword to call the assembly routine.

Simple Assembly

Our simple assembler code is to change the border color and get it rapidly flashing and there are three ways I’m going to accomplish it in this lesson.

1) Z88dk C method.

2) Creating an assembly variable and modifying that value in C.

3) Passing a variable with a function to the assembly code.

The final method is my preferred method and I will duplicate this method in different memory SECTIONS to show that the functions actually are in those portions of memory.

Our simple assembly code is found in ASM Sources > Contended > ramlow.asm and looks like this:

PUBLIC _Border_color
_Border_color:
    defb 0;black

PUBLIC _border_Change_contended1
_border_Change_contended1:
    ld a, (_Border_color)
    and 7       ; mask with 0000 0111
    out (254), a
ret

PUBLIC _border_Change_contended2
_border_Change_contended2:
    ld a, l     ;contents of L are loaded into A
    and 7       ; mask with 0000 0111
    out (254), a
ret

You will notice that there are 2 assembly functions here and 1 assembly variable. The first assembly routine is _border_Change_contended1 and uses the assembly variable _Border_color. The second is slightly more obfuscated. It uses register L to obtain the value. Both functions perform the same action, it changes the border color and is written for simplicity’s sake only.

How do we call either one of these assembly functions? Just like our last lesson, the trick is to use the extern.h found in headers > uncontended > externs.h.

This has been changed from our last lesson and now looks like this:

#ifndef EXTERNS_H
#define EXTERNS_H
//UNCONTENDED RAM


//VARIABLES
extern unsigned char Variable_in_contended;//found in ramlow.asm
extern unsigned char Variable_in_Bank0;//found in ram0Z.asm


//FUNCTIONS
//found in ramlow.asm
extern unsigned char Border_color;


//found in ramlow.asm
extern void __FASTCALL__ border_Change_contended1();
extern void __FASTCALL__ border_Change_contended2(unsigned char color);


//found in uncontended_section.asm
extern void __FASTCALL__ border_Change_contended3(unsigned char color);


//found in RAM0Z.asm
extern void __FASTCALL__ border_Change_contended4(unsigned char color);


/*
__fastcall__
 Fastcall linkage allows one parameter
 to be passed by register in a subset of DEHL.
 So if the parameter is 8-bit, the value will
 be in L.  If the parameter is 16-bit,
 the value is in HL and if it is
 32-bit the value is in DEHL.
*/


#endif

I have added several new externs. The first one is the assembly variable Border_color, remember that the assembler functions always must have a leading underscore (_) in front of them, but are called in C without the leading underscore. See RULE 1 from the last article. Be sure to always comment where the variable is found. The same applies to functions, don’t make your life more difficult when trying to trace out the program later on.

The next thing that should be noticed is the next two EXTERNS. Again, remember that an EXTERN just is a placeholder within the main function that declares a space, but is not the actual function itself.

extern void __FASTCALL__ border_Change_contended1();
extern void __FASTCALL__ border_Change_contended2(unsigned char color);

These are our calling functions and linkage from C to assembly. The first uses no input and is dependent on a global variable within the assembly, while the second uses input and requires no variable and uses the stack to push the variable.

The final thing you should notice is the assembler’s comment.

/*
__fastcall__ 
 Fastcall linkage allows one parameter
 to be passed by register in a subset of DEHL.
 So if the parameter is 8-bit, the value will
 be in L.  If the parameter is 16-bit,
 the value is in HL and if it is
 32-bit the value is in DEHL.
*/

This little handy note comes from Allen Albright. Whenever a fastcall is used, we can pass 1 parameter from an 8 bit(L), a 16 bit(HL), or a 32 bit(DEHL). This is exactly what the EXTERN

extern void __FASTCALL__ border_Change_contended2(unsigned char color);

is doing. Since we are just pushing an 8-bit variable, we are using only L. During a FASTCALL operation, Z88dk will push the current value on L onto the stack before using L and pop it back when it is done. FASTCALL is the only assembly function that performs a cleanup, so if you perform any other types of assembly functions, be prepared to clean up the registers that you use.

PROGRAM FLOW

Let’s follow the program flow for an easy explanation. Browse to Sources > Uncontended > uncontended.c. Look for the lines:

    while(1)
    {
        Border_test();
    }

This endless loop constantly calls the function Border_test(). Border test is found in Headers > Uncontended > uncontended.h. The function looks like this:

void Border_test (void)
{
    if (in_key_pressed( IN_KEY_SCANCODE_1 ))
    {
        ava = 1;
    }
    if (in_key_pressed( IN_KEY_SCANCODE_2 ))
    {
        ava = 2;
    }
    if (in_key_pressed( IN_KEY_SCANCODE_3 ))
    {
        ava = 3;
    }
    if (in_key_pressed( IN_KEY_SCANCODE_4 ))
    {
        ava = 4;
    }
    if (in_key_pressed( IN_KEY_SCANCODE_5 ))
    {
        ava = 5;
    }
    if (in_key_pressed( IN_KEY_SCANCODE_0 ))
    {
        ava = 0;
    }

    if (ava == 1) //first test
    {
        zx_border(INK_BLACK);
        zx_border(INK_BLUE);
    }

    if (ava == 2) //second test
    {
        Border_color = 0;
        border_Change_contended1();
        Border_color = 2;
        border_Change_contended1();
    }

    if (ava == 3) //third test
    {
        border_Change_contended2(0);
        border_Change_contended2(3);
    }

    if (ava == 4) //fourth test
    {
        border_Change_contended3(0);
        border_Change_contended3(4);
    }

    if (ava == 5) //fifth test
    {
        border_Change_contended4(0);
        border_Change_contended4(5);
    }

    if (ava == 0) //reset test
    {
        zx_border(INK_WHITE);
    }
}

The in_key_pressed() function is introduced with #include <input.h> found in uncontended.c and allows you to read the keyboard.

When key 1 is pressed, it will set the variable ava to 1. Below, if ava is set to 1, it will call the Z88dk function zx_border with the color Black and then follows it up with Blue.

When key 2 is pressed, it will set the variable ava to 2. Below, if ava is set to 2, it will set the global variable Border_color to 0. The C program wouldn’t normally know what the variable Border_color is, but because we told the compiler that the namespace Border_color is an external variable (EXTERN) when the program was compiled and linked via an object file, it knows that Border_color is actually in ramlow.asm. Border_color is set to 0. The next line border_Change_contended1(); is then called. Again, Z88dk within the uncontended.c does not know what the function border_Change_contended1() is, but since it was declared as an EXTERN, it knows to look elsewhere. Since border_Change_contended1() was linked as an object file, the program knows to look at ramlow.asm. It will then find the routine:

PUBLIC _border_Change_contended1
_border_Change_contended1:
    ld a, (_Border_color)
    and 7       ; mask with 0000 0111
    out (254), a
ret

Since the assembly function is declared as PUBLIC, this routine is visible to the contended.c program which in turn is visible to the uncontended.c program, all happening because of the object files and linking. In the assembly function, we set the register A to the public variable Border_color, we perform and out with the number stored in A and then return back to the main program in uncontended.h. Back in uncontended.h, we set the Border_color to 2 and repeat.

When key 3 is pressed, it will set the variable ava to 3. Below, if ava is set to 3, it will call the function border_Change_contended2(0). This function passes the number 0 to the function without using a global variable. Again, like border_Change_contended1(), Z88dk does not normally know what or where border_Change_contended2 is located, but since we defined it as an EXTERN and linked the object file from contended, it will know to look for the function in contended. The program will then push the contents of the register HL to the stack, set a new value in the register L of HL to 0, and then call the function border_Change_contended2. The assembly looks like this:

PUBLIC _border_Change_contended2
_border_Change_contended2:
    ld a, l     ;contents of l are loaded into a
    and 7       ; mask with 0000 0111
    out (254), a
ret

Here, register A is loaded with the contents of register L. The program will perform an OUT to port 254 with the value found in register A. The value of HL which we had changed at the start is then restored to what the original value was. We then return to uncontended.h.

The next line in uncontended.h is border_Change_contended2(3). It will repeat as above, but with setting the value of register L to 3 (magenta).

I would like to point out that the only difference between border_Change_contended2, border_Change_uncontended and border_Change_RAM0 is where they are placed in memory.

To verify, open the compiled.map in your work root directory and you will find border_Change_contended2 is at address $5E90, border_Change_uncontended is at address $8B69 and border_Change_RAM0 is at address $C001. All of this is because of mmap.inc and the SECTIONS mentioned in my previous articles.

Here are the results when pressing keys 1 through 5. What I find interesting is that the number of lines displayed in each one are that different especially with border_Change_uncontended and border_Change_RAM0 which are pretty much the same function with the only difference is where in the uncontended memory each of them lies.


@page { size: 8.5in 11in; margin: 0.79in } p { margin-bottom: 0.1in; line-height: 115%; background: transparent } a:link { color: #000080; so-language: zxx; text-decoration: underline }
zx_border()

@page { size: 8.5in 11in; margin: 0.79in } p { margin-bottom: 0.1in; line-height: 115%; background: transparent } a:link { color: #000080; so-language: zxx; text-decoration: underline }
border_Change_contended1()

@page { size: 8.5in 11in; margin: 0.79in } p { margin-bottom: 0.1in; line-height: 115%; background: transparent } a:link { color: #000080; so-language: zxx; text-decoration: underline }
border_Change_contended2()

@page { size: 8.5in 11in; margin: 0.79in } p { margin-bottom: 0.1in; line-height: 115%; background: transparent } a:link { color: #000080; so-language: zxx; text-decoration: underline }
border_Change_RAM0

border_Change_RAM0()

That’s about all for this article, our main point was to show you that you can (within the Z88dk framework) have assembly code functions in different memory sections and how to use the FASTCALL feature within Z88dk.

Here are a few rule that should be followed with Z88dk

Rule 1: Assembler Variables and Functions should have a leading underscore before the variable name

Rule 2: Once in the C code, be sure to remove the leading underscore to call the Function or Variable

Rule 3: If you want the Variable or Function to be visible in C, make sure that the Variable or Function is let to PUBLIC

Rule 4: Make sure that the names are not duplicated across SECTIONS

Rule 5: Fastcall only pushes a single parameter as DEHL

As always, happy coding.

Author: andydansby

I'm a hobbyist coder working with the ZX Spectrum. Living in New York state near the Syracuse area. I grew up in Virgina. The first computer I owned was a TRS-80 given to us, my parents bought me was a Timex Sinclair 2068 and the Sinclair bug was set for my childhood.

One thought on “128k Programming basics using Z88DK and the SCCZ80 compiler Lesson 3”

Leave a comment