128k Programming basics using Z88DK and the SCCZ80 compiler Lesson 2 part 2

OBJECT files and linking to associate sections

This is our second lesson, part 2 of programming the 128k ZX Spectrum. I have split up this lesson into several parts as I have greatly expanded the source code. In my last part, I covered the many batch files that I am using to compile the separate files

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

First, let’s download the project files, they are available at https://github.com/andydansby/ZX_Spectrum_128k_programming_lesson2. The IDE that we are going to be using is CodeBlocks (available at https://www.codeblocks.org/) though you can still use notepad ++ if you prefer it.

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 lessons will be written as if you are using the same directories as I am.

There’s no place better to start than the beginning, in Code::Blocks open up the directory tree to Sources > UNCONTENDED > uncontended.c. Our code looks like.

// using sccz80 1.99c

#include <arch/zx.h>
#include <stdio.h>//standard input output
//#include <input.h>
//#include <stdlib.h>//standard library

#include "variables.h"
#include "externs.h"
#include "uncontended.h"

void main ()
{
    zx_cls(PAPER_WHITE | INK_RED);
    printf ("Main () running in uncontended\n");

    printf ("_Variable_in_contended = %d\n", Variable_in_contended);
    printf ("_Variable_in_Bank0 = %d\n", Variable_in_Bank0);

    printf ("tom = %d\n", tom);
    printf ("dick = %d\n", dick);
    printf ("harry = %d\n", harry);
    printf ("eve = %d\n", eve);
    printf ("bob = %d\n\n", bob);


    harry = add_two_numbers (tom, dick);
    printf ("Add 2 numbers %d + %d = %d\n", tom, dick, harry);

    harry = subtract2numbers (Variable_in_Bank0, eve);
    printf ("Subtract 2 numbers %d - %d = %d\n", Variable_in_Bank0, eve, harry);

    harry = multiply2numbers (Variable_in_contended, bob);
    printf ("Multiply 2 numbers %d * %d = %d\n", Variable_in_contended, bob, harry);

    harry = add_two_numbers (Variable_in_contended, Variable_in_Bank0);
    printf ("Add 2 numbers %d + %d = %d\n", Variable_in_contended, Variable_in_Bank0, harry);

    while (1);
}

//must have blank line at end

Here is our program’s main entry and at the end an endless loop.

The includes in this file are arch/zx.h, stdio.h, variables.h, uncontended.h and externs.h

Within main(), we clear our screen and then we print that we are “running in uncontended”.

Right now, the real question is how do you know that you are in uncontended at the moment? The answer lies in the map file in the root directory. Using notepad ++, open the file compiled.map and search for _main. You will see the following.

_main                           = $88CD ; addr, public, , ramALL, code_compiler, uncontended.c:98

You will see that the main() is running at hex address $88CD which 35021 which is solidly within contended RAM.

Our next line

printf ("_Variable_in_contended = %d\n", Variable_in_contended);

Will print a variable that is found in contended. The definition and assignment of the variable “ Variable_in_contended” is found in ASM Sources > Contended > ramlow.asm, you will see that “_Variable_in_contended” is assigned a value of 33. Again, you can prove it by looking in the compiled.map file that it is indeed in contended with the line.

_Variable_in_contended          = $5E88 ; addr, public, , ramALL, CONTENDED, ramlow.asm:28

Which is found at address 24200, indeed not only in contended but at the very start of contended.

We will find the same thing for the line

printf ("_Variable_in_Bank0 = %d\n", Variable_in_Bank0);

Which is found in your compiled.map file with the line

_Variable_in_Bank0              = $C000 ; addr, public, , ramALL, BANK_00, ram0Z.asm:28

Which is found at address 49152 indeed not only in RAM0 (home bank), but at the very start of RAM0.

How we place those variables will be explained a little further on.

Our next four lines

    printf ("tom = %d\n", tom);
    printf ("dick = %d\n", dick);
    printf ("harry = %d\n", harry);
    printf ("eve = %d\n", eve);
    printf ("bob = %d\n\n", bob);

Are printing our local variables or variables found within variables.h in Headers > Uncontended > variables.h. We know their placement because we can look it up in our map and they are found from $8B65 to $8B6A.

Our next two lines show that we can perform a simple routine and add two numbers together that are found in the local variable set.

    harry = add_two_numbers (tom, dick);
    printf ("Add 2 numbers %d + %d = %d\n", tom, dick, harry);

The next two lines should be of interest, we are now taking a variable found in the home ram (ram 0) and a local variable and are using them in a function.

    harry = subtract2numbers (Variable_in_Bank0, eve);
    printf ("Subtract 2 numbers %d - %d = %d\n", Variable_in_Bank0, eve, harry);

Now, we can try the same with a variable in contended ram and a local variable.

    harry = multiply2numbers (Variable_in_contended, bob);
    printf ("Multiply 2 numbers %d * %d = %d\n", Variable_in_contended, bob, harry);

Finally, we test with a variable in contended and one found in home ram (ram0)

    harry = add_two_numbers (Variable_in_contended, Variable_in_Bank0);
    printf ("Add 2 numbers %d + %d = %d\n", Variable_in_contended, Variable_in_Bank0, harry);

How this is all done

When we compiled the contended section and the RAM 0 section, we defined the variables in assembler.

Have a look at the file found in ASM Sources > Contended >ramlow.asm and the file in ASM Sources > RAM0 > ram0Z.asm. You will see the following respectively.

SECTION CONTENDED
PUBLIC _Variable_in_contended
_Variable_in_contended:
    defb 33
SECTION BANK_00
    PUBLIC _Variable_in_Bank0
    _Variable_in_Bank0:
    defb 55

You will then link the assembler to the C code by compiling using the list file found in Others > Contended > ramlow.lst, Others > RAM0 > ram0.lst and Others > Uncontended > rammain.lst

EXTERN

Externs are used to declare and reserve a namespace and room in an unincluded area from your main C program. What this means is that if you declare an EXTERN, the compiler knows that this is a name of a variable or routine that is found in another area that’s not part of the program you are compiling.

Take a look at Headers > Uncontended > externs.h

#ifndef EXTERNS_H
#define EXTERNS_H
//UNCONTENDED RAM
extern unsigned char Variable_in_contended;//found in ramlow.asm
extern unsigned char Variable_in_Bank0;//found in ram0Z.asm
#endif

This tells the compiler that you are declaring a namespace for the variables Variable_in_contended and Variable_in_Bank0 but that the variables exist externally to the program you are currently compiling. I find it helpful to place a comment behind the external variable to remind us where the variable originates.

So time to define some rules

RULE 1: assembler variables are defined with an underscore in front of them

You still call these variables from C code without the leading underscore, but they must be defined with an underscore in assembler.

RULE 2: Assembler files are attached to a C file using a list file.

The assembler variables are assigned and linked to the local code for that section within the list of compiled code. This is done without ramlow.lst and ram0.lst found in Others > Contended and Others > RAM0. Have a quick peek at those files and you will see something like:

ram0.c
ram0Z.asm
; .lst files commented with semi-colon or hash
# must have blank line after this

The lst file is considered when placed in the compile line to compile that section.

zcc +zx -vn -SO3 -c -clib=new –fsigned-char -o contended.o @ramlow.lst

This is indicated by using the @ symbol in the compile line followed by the list name.

RULE 3: Variables (and code) are visible to the main C file only after those files are compiled as OBJECT files first

Object files are made by using the -o flag in the compilation string and optionally naming the file.

zcc +zx -vn -SO3 -c -clib=new –fsigned-char -o contended.o @ramlow.lst

RULE 4: Use the EXTERN keyword to declare the variable is created elsewhere.

extern variable_size variable_name;//actual location of the variable.
This is also required for dataspace, chunks of data, or functions.

So when you are ready to compile the program, open up the Command Prompt (admin), change your directory to the proper directory and type “compile”. In about a minute, you will have your compiled program, run it in your favorite emulator.

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.

Leave a comment