Increasing code memory in EvoSDK - how to expand the code memory area to very large volumes.

Increasing memory for code in EvoSDK
 by Hippiman

In my previous article (Info Guide #11) I mentioned that
approximately 32K is available for code and variables in EvoSDK, as well as about
that this volume is quite enough for small programs, but
for larger ones you have to resort to various tricks
(for example, transfer part of the data to extended memory). But this
still not enough. C code compiles quite well
sweepingly, which means that when writing a large game in
In any case, you will have to squeeze it in somehow.

I have developed a way to expand the code memory area to
very large volumes. Briefly the essence of the method:

1. Part of the code that does not fit into the main memory
compile separately;
2. Convert to a binary file;
3. Throw it onto a floppy disk;
4. At the right time, load this file into extended memory;
5. From there we mount it in the 2nd slot (0x8000-Oxbfff);
6. Call this code from the main program.

In essence, this approach is very similar to dynamically connected
libraries (.dll), but with some restrictions.

There is one “advantage” of this method, but it is very large: the amount of code is no longer
limited, you can write a lot.
There are several “disadvantages”: difficulty in use and preparation,
slight slowdown in the entire program, some
restrictions on loaded modules.

                Preparing the plugin

The first step is to fix /evosdk/_compile.bat. In this file
you need to remove or comment out the line rd /s /q %temp% so that
after compiling the project, the directory with temporary files was not deleted
files that we really need.
The next step is to extract the function addresses from the file
out.map, which is located in the _temp_ directory. For this I
I use this little Perl script:

#!/usr/bin/perl
use strict;

my $str;
my @arr;
open FIL,"_temp_\out.map";
open OUT,">map.h";

while($str=)
{
    chomp $str;
    $str=~s/s*//;
    if(length($str)>0)
    {
        @arr=split(/ /,$str);
        if(substr($arr[2],0,1) eq "_")
        {
            print "#define " .$arr[2]." 0x". $arr[0]."n";
            print OUT "#define " .$arr[2]." 0x". $arr[0]."n";
        }
    }
}

At the output of this script we get the file map.h, which will be needed
connect to the future library.
Ultimately, the compiled library should have
connectedthe following headers:

#include "..evosdkevo.h"
#include "..evosdkstartup.h"
#include "..Parent npoektresources.h"
+ all necessary additional headers from the parent project.

Now we need to teach the child project to understand the functions from
parental.
To do this, we write similar constructions for each function that
we will use.

#define draw_image ((void(*)(u8,u8,u8))_draw_image)
#define select_image ((void(*)(u8))_select_image)
#define draw_tile_key ((void(*)(u8,u8,u16))_draw_tile_key)

#define put_mem ((void(*)(u8,u16,u8))_put_mem)
#define get_mem ((u8(*)(u8,u16))_get_mem)
etc.

After which the declared functions can be used in
project in the same way as usual.

Next, we write the main code of the plug-in module. However, in order not to
calculate the entry address of the main function after each change,
it is better to write all other functions after it (naturally,
having announced in advance).

                 Preparation of the main program

The logic of how the main program works with the module is as follows: file or
files from the floppy disk are loaded into memory, and then at the right time
are connected to the 2nd window and called at address 0x800A.

As an example of connecting and calling, you can take this
function:

void manager(u8 page,u8 operation,void *a,u8 b) __naked
{
u8c;
put_mem(trigger_text_page,exchangedate_begin,operation);
put_memw(trigger_text_page,exchangedate_begin+1,(u16)a);put_mem(trigger_text_page,exchangedate_begin+3,b);
//--------------------------------------------------
__asm
    push ix
    ld ix,#0
    add ix,sp
    ld a,4 (ix)
    PUSH AF
    xor #0x7f
    LD BC, #Oxbff7
    ld(_MEMSLOT2),a
    OUT (C), A
    POP AF
    call #0x800a
    pop ix
    ret
__endasm;

}

page - the number of the page on which the binary is located,
operation - operation identifier (manager on the other side
distributes input parameters and calls the desired function in
depending on this identifier),
A - typeless pointer - parameter,
B is another parameter.

Pay attention to lines like

put_mem(trigger_text_page,exchangedate_begin,operation);

When called, the connected library begins to behave like
independent program and does some nasty things with
stack, resulting in the function instead of the transferred data
gets something completely different. However, this does not affect
subsequent performance of the entire project. Digging hard into
these processes did not begin. If anyone manages to figure out how
It’s nice to pass parameters to a child function, I’ll just
glad.

Ultimately I took a workaround. If parameters are passed
it is impossible, but it is very necessary, then you can use an external one
buffer. The role of which is performed by extended memory.

                       Preparing the EVO SDK

The problem is that if you build and run this project, then
nothingwill work, since most of the functions of EvoSDK,
who use the 2nd window do not “clean up after themselves”. They don't connect
back and forth the page that was in the 2nd window before they were called.

To edit, open put_get_mem_atm.h, lib_sprites.asm and
lib_tiles.asm.
Add the necessary functions to the beginning and end:

In put_get_mem_atm.h :
to the beginning:
push ix
ld ix,#0
add ix,sp
to the end:
pop af
LD BC, #Oxbff7
ld(_MEMSLOT2),a
out(c),a

In lib_sprites.asm and lib_tiles.asm :
to the beginning:
ld a,(_memSlot2)
push af
to the end:
pop af
ld(_memSlot2),a
ld bc,MEM_SLOT2
out(c),a

This is a creative process, because not all functions switch
memory pages and not all of them will be used in your
program.

List of functions that definitely need to be fixed:
_DOS_3D13, sprites_start, sprites_stop, draw_tile,
draw_tile_key, draw_image, pal_select, swap_screen, all functions
from put_get_mem_atm.h.

I also note that after making edits and rebuilding the libraries
EvoSDK may stop working. This will happen because
exceeding the maximum library size. In this case you can
comment out any "unnecessary" functions, for example
_sample_play in lib_sound.asm. This function is responsible for covox,
which I don't use.

                      Assembly and compilation

Everything is not so simple here either. To compile a child project
you need to use SDCC version 2.9. Newer versions contain
a bug that prevents you from explicitly specifyingpointers to
functions, and therefore the library turns out to be a “thing in itself”.
We will not be able to call functions of the parent project.

You need to call sdcc with the following parameters:
sdcc -mz80 --code-loc 0x8000 --data-loc OxBCOO main.c
-o %temp%out.ihx

We place the code exactly from address 0x8000, since if we place it from
0x0000, then all function pointers will have references starting from
this address. And we don’t need this, because from 0x0000 we have addresses
will be the main program code.

SDCC generates compiled code in Motorola hex format. For
to obtain the binary I use the hex2bin.exe utility
(http://sourceforge.net/projects/hex2bin/ ).

But that's not all. The code in the resulting file is as we indicated
when compiled, starts at address 0x8000, and the rest is
filled with garbage.
To trim the binary, I wrote a small utility in java.

For those interested, here is the code:

package cutter;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author VSurjenko
 */
public class Cuter {

    public static void main(String[] args)
    {
        try {
            String filename,destfile;
            int addr,a;
            byte arr[];
            byte arr2[];

            if(args.length>0)
            {filename=args[0];
            }
            else
            {
                System.out.println("Program needs 3 parameters:"
"file name, destination file name and address of cutting.");
                return;
            }
            if(args.length>1)
            {
                destfile=args[1];
            }
            else
            {
                destfile=filename.concat("_cuted");
            }   if(args.length>2)
            {
                addr= Integer.parseInt(args[2]);
            }
            else
            {
                addr=0x8000;
            }
            //------------------------------------
            Path path = Paths.get(filename);
            Path path2 = Paths.get(destfile);
            arr=Files.readAllBytes(path);
            arr2=new byte[arr.length-addr];
            for(a=0;a
2. We pull function pointers from Out.map;
3. We compile the child program, convert it to a binary file from
hex format, cut it off and throw it into the main directory
program;
4. We assemble the entire project into a floppy disk image;
5. Testing.

Now briefly about the limitations of the child module.

1. All functions that can change the memory page in the 2nd window
must be in the parent project. Otherwise they
will spoil themselves. This means that the functions of working with
files cannot be transferred to the loaded module;
2. The size of one module is limited to 16K - one page. This is not
shouldbe a big problem. You can always make several
modules and connect the one you need at the moment.
3. I still haven’t found a way to “make friends” of the loadable module with
global variables of the main program.

You will most likely have to spend some time with the debugger,
but the result will be worth it. Of course, frequently called code
It’s better not to put it in a separate binary, but there will always be
voluminous functions that are not used most of the time.

For example, the manual code from SpaceMerc Liberation is 8377
byte, I moved the text output function to a separate binary,
and together with the call function and the expanded EvoSDK library code
began to take up 6890 bytes. Almost 1.5K net benefit

Share your thoughts about the article