Page 1 of 1

FRAMES system variable with SDCC

Posted: Sun Aug 20, 2017 2:33 pm
by marcov
Hello,

in my experience with z88dk/sccz80, I successfully used FRAMES (23672) system variable for timing management in my Spectrum apps.
Now I am trying z88dk with sdcc and it seems to me that every access to FRAMES system variable always gets the same value. :mad:

As a comparison, I prepared 2 versions of a test program and ran them in fuse and zesarux emulators.

The sccz80 version:

Code: Select all

// zcc +zx -lndos  frames_sccz80.c -o frames_sccz80 -create-app

#include <stdio.h>

extern unsigned int frames(23672);

void main()
{
        unsigned int j = 0;
        unsigned char i = 0;
        
        for (i=0; i<10; i++)
        {
                printf("%u\n", frames);
                
                for (j=0; j<1000; j++);
        }
}
works as expected, showing a list of different values.

In the sdcc version:

Code: Select all

// zcc +zx -startup=1 -clib=sdcc_iy frames_sdcc.c -o frames_sdcc -create-app

#include <stdio.h>
#include <cpu.h>

void main()
{
        unsigned int j = 0;
        unsigned char i = 0;
        unsigned int* frames = 23672;
        
        for (i=0; i<10; i++)
        {
                printf("%u %u \n", cpu_wpeek(23672), *frames);
                
                for (j=0; j<10000; j++);
        }
}
I tried accessing FRAMES value with either pointer and peek function, but in both cases I always get the same value.
I am using a recent z88dk nightly build on windows.

Which is the correct way of accessing the FRAMES counter with sdcc?
Are there better ways for timing management rather than FRAMES?

Thanks to all z88dk developers for excellent work!

marco

Posted: Sun Aug 20, 2017 4:14 pm
by alvin
The first compile:

// zcc +zx -lndos frames_sccz80.c -o frames_sccz80 -create-app

is using the classic library (there is no clib=new, clib=sdcc_ix and clib=sdcc_iy on the compile line). The classic library does not disable interrupts on startup by default.


The second compile:

// zcc +zx -startup=1 -clib=sdcc_iy frames_sdcc.c -o frames_sdcc -create-app

is using the new library (clib=sdcc_iy, also clib=new, clib=sdcc_ix)

A compile with sccz80 and the new lib would be:

// zcc +zx -startup=1 -clib=new frames_sdcc.c -o frames_sccz80 -create-app


Now the issue :)

The new library disables interrupts on startup. So the frames variable will not change because the basic interrupt routine is not running. This is done deliberately because sdcc is not compatible with the basic interrupt routine. The reason is sdcc will modify the iy register even when we tell it not to (clib=sdcc_iy tells it not to by adding--reserve-regs-iy to sdcc's compile line). There are circumstances where sdcc cannot generate code unless iy is available so it will use iy in those times. The basic interrupt routine pokes into memory at an offset from iy so if sdcc is changing iy then you got random pokes in memory which will lead to very confusing crashes.

Basic's interrupt routine doesn't really do anything for c or asm except to suck up cycles so disabling it is usually what you want to do anyway. In order to have a time variable to replace frames, what is done is to create your own im2 interrupt routine to do that. Next post I will give some example code.

Posted: Sun Aug 20, 2017 4:40 pm
by alvin
Here's an example:

Code: Select all

// sdcc compile with newlib
// zcc +zx -startup=1 -clib=sdcc_iy frames_sdcc.c -o frames_sdcc -create-app
//
// sdcc compile with newlib and SO3 to enable aggressive rules to get rid of some facepalms in generated code
// zcc +zx -startup=1 -clib=sdcc_iy -SO3 frames_sdcc.c -o frames_sdcc -create-app
//
// sdcc compile with max optimization = much slower compile times
// zcc +zx -startup=1 -clib=sdcc_iy -SO3 --max-allocs-per-node200000 frames_sdcc.c -o frames_sdcc -create-app
//
// sccz80 compile with newlib
// zcc +zx -startup=1 -clib=new frames_sdcc.c -o frames_sdcc -create-app
//
// There is a new startup=30 that is lighter weight as it uses the rom to print
// There is no scanf stream when this startup is used.
// zcc +zx -startup=30 -clib=sdcc_iy frames_sdcc.c -o frames_sdcc -create-app

// Pragmas modify the crt's startup and initialization.
// The pragmas are stored in file "zcc_opt.def" as the program is compiled
// and the crt will read that file to learn of options different from the defaults.
// Judicious use of pragmas can eliminate features you're not using and reduce binary size.

#pragma output CRT_ORG_CODE = 0x8184   // move org just above the im2 table and jump (see below)
#pragma printf = "%u"                  // let's cut out the printf converters you're not using

// In large programs it's usually better to put pragmas in a standalone file like "zpragma.inc"
// and then add that to the compile line with "-zpragma-include:zpragma.inc"

#include <stdio.h>
#include <string.h>        // memset
#include <im2.h>           // im2 macros and functions
#include <intrinsic.h>
#include <z80.h>           // bpoke, etc

// frame counter

unsigned int frames;

// There are a lot of ways to define an interrupt service routine
// This one is being done with a macro.  What makes a regular function
// different from an ISR is that the ISR must preserve cpu registers
// it is using and enable interrupts before returning with the special
// instruction "reti".  This macro creates a function called "isr" that
// does these things.  The 8080 in its name indicates it only saves
// the main registers af,bc,de,hl.  If you have code in there that modifies
// other registers (including c library code) you can use the IM2_DEFINE_ISR
// macro instead as that will save all of the z80's registers.  This
// is also a place where you can put ay music, etc.

IM2_DEFINE_ISR_8080(isr)
{
   // update the clock
   ++frames;
}

void main()
{
    unsigned int j = 0;
    unsigned char i = 0;
    
         // set up im2 routine
         // newlib has interrupts disabled at this point
         
         im2_init((void*)0x8000);           // set z80's I register to point at 0x8000, set im2 mode
         memset((void*)0x8000, 0x81, 257);  // make a 257-byte im2 table at 0x8000 containg 0x81 bytes

         // On interrupt the z80 will jump to address 0x8181

         z80_bpoke(0x8181, 0xc3);                // z80 JP instruction
         z80_wpoke(0x8182, (unsigned int)isr);   // to the isr routine
         
         // Now enable interrupts
         
         intrinsic_ei();  // inlining ei would disturb the optimizer
         
    for (i=0; i<10; i++)
    {
        printf("%u\n", frames);
        
        for (j=0; j<10000; j++);
    }
         
         // Returning to basic will crash because the org has moved to 0x8184
         // and the basic loader that z88dk is providing is doing "CLEAR 0x8183".
         // This is not low enough because we are putting stuff at 0x8000 and with
         // that clear address, that stuff is overwriting some of basic's stack.
         // So a return with crash because basic's stack has been contaminated.
         // You can write your own loader to fix this problem if you want to return
         // to basic or you can place the im2 table at a higher address but I and a lot
         // of people like placing the im2 table below the program.
         
         while (1) ;
}

Posted: Sun Aug 20, 2017 6:28 pm
by marcov
Hi Alvin,
thank you very much for your prompt and detailed reply :-)
I thought about interrupts but could not figure out how to set them up; I will play with your suggested solution.
Thank you and best regards

m.

Posted: Tue Jan 02, 2018 12:15 pm
by thricenightly
I've been playing with this code and other interrupt bits and pieces, and it all works fine. But I can't work out how to exit back to BASIC with the interrupts left at mode 2. I can see in Fuse's debugger that it goes back to IM1 after the "return 0;" at the end of my program.

My interrupt code's still in memory, so it should still work once we're back in BASIC shouldn't it? Is there a pragma or something to leave IM2 active on exit?

Posted: Tue Jan 02, 2018 2:47 pm
by alvin
thricenightly wrote:My interrupt code's still in memory, so it should still work once we're back in BASIC shouldn't it? Is there a pragma or something to leave IM2 active on exit?
The return to im1 mode is hard coded into the crt:
https://github.com/z88dk/z88dk/blob/mas ... sm.m4#L223

Maybe there should be a pragma to change that behaviour. In the meantime you can comment that "im1" out or you can add your own pragma:

Code: Select all

IFNDEF CRT_KEEP_IM
   im 1
ENDIF
   ei
   ret
Then if you define the "CRT_KEEP_IM" as non-zero im1 will not be set on exit. Unfortunately each crt has its own copy of this code so it will only work in crts you modify.

Basic does require the code at 0x38 to run on each interrupt so your interrupt routine must jump to 0x38 at its end. If you're running that code, the IY register is being used by the basic system, which means sdcc is not compatible with it. Maybe something fancier has to be done like enabling a jp to 0x38 in the crt's exit code.

Posted: Tue Jan 02, 2018 5:22 pm
by thricenightly
alvin wrote:Basic does require the code at 0x38 to run on each interrupt so your interrupt routine must jump to 0x38 at its end. If you're running that code, the IY register is being used by the basic system, which means sdcc is not compatible with it. Maybe something fancier has to be done like enabling a jp to 0x38 in the crt's exit code.
Sorry to be dumb, but I don't quite follow that last bit. My understanding is that the Spectrum's ROM code at 0x38 uses the IY register, and so does the sdcc compiled code, so they will clash. But if my code has the IM2 vector then when the interrupt fires it's my code which runs, right? And it does so with interrupts disabled? So can't I just save the current IY register at my entry point, run my code, restore IY when I'm done, then jump to 0x38 with the IY content as the ROM code expects it? Wouldn't the ROM code then run with the IY register as it expects it, re-enable interrupts and exit normally, all none the wiser for my IY-using code getting in before it?

Posted: Wed Jan 03, 2018 1:19 am
by alvin
Well you can't do this:

your_im2_isr:

push regs
do stuff
pop regs
jp 0x38

because the jp to 0x38 will use the current value of iy which will likely not point into the system vars area.

But you could do this:

your_im2_isr:

push regs
do stuff
pop regs except iy
ld iy,0x5c3a
call 0x38 ;; there is an ei reti at the end of the basic isr
pop iy
ret

Posted: Wed Jan 03, 2018 7:00 pm
by thricenightly
OK, that works. I crudely hacked the "im1" out of the CRT 31 code, then redefined the ISR wrapper macro:

Code: Select all

#define IM2_DEFINE_ISR_WITH_BASIC(name)  void name(void) __naked \
{ \
        __asm \
        EXTERN        asm_im2_push_registers \
        EXTERN        asm_im2_pop_registers \
        \
        call        asm_im2_push_registers \
        call   __im2_isr_##name \
        call   asm_im2_pop_registers \
        \
        push iy \
        ld iy,0x5c3a \
        call 0x0038 \
        pop iy \
        ret \
        __endasm; \
} \
void _im2_isr_##name(void)
With a spot of help from the guys at SpectrumComputing who helped me properly understand the involvement of the IY register, I can see how that (appears to) work correctly.

If that looks OK to you I'll try to formalise the approach with a pragma as discussed above.

Posted: Thu Jan 04, 2018 3:40 am
by alvin
Yeah that looks ok!