ZX80 development - some questions

ZX80, ZX 81, ZX Spectrum, TS2068 and other clones
ShaunB
Member
Posts: 41
Joined: Mon Jan 02, 2012 6:13 pm

ZX80 development - some questions

Post by ShaunB »

Hi,

Now I have some free time again, I've decided to start making 8-bits once more; as I don't do much assembly, z88dk is obviously the best choice.

The current idea that I'm developing is a Sunday League Football Management Simulator for the Sinclair ZX80 with 4K ROM and 16K RAM.

When I previously used z88dk for the Sinclair ZX81, I could include my own header file with my own pre-processor symbols defined. This was circa 2013 so things will have changed since then.

When I do on the latest build, I am unable to use anything that I've defined, for instance, say my zx80.h file has this:

Code: Select all

    #define SCRLOC(X,Y)        ((Y<<5)+X)
    #define INVERSE(A)        (0x80 | A)
    #define EOL                        0x76
    #define EOF                        0xff
when I use SCRLOC in any function in my main.c file, I get an error telling me that SCRLOC is not defined even though I've included <zx80.h> in that file. This is no biggie, as I can simply copy paste those pre-processor symbols over. But this was working in previous version (like in the API thing I was developing here).

However, let's say I have something like this:

Code: Select all

    #include <stdio.h>
    #define SCRLOC(X,Y)        ((Y<<5)+X)
    
    int main();
    int printAt(unsigned char xy);

    int main()
    {
        unsigned char cursorPos = SCRLOC(8,8);
        printAt(cursorPos);
        gets();
        return 0;
    }
    
    printAt(unsigned char xy)
    {
        #asm
        push hl
        push bc
        ld bc, $0000
        ld hl, $0004
        add hl, sp               ;; HL should now be pointing at my unsigned char
        ld (BUFFER), hl        ;; Store the value of our unsigned char in the BUFFER
        ld hl, ($400c)          ;; Get the screen pointer
        inc hl                     ;; We don't care about the first screen location as this
                                     ;; should always be a NEW LINE in ZX80 terms

        ld bc, (BUFFER)       ;; Write the value of the unsigned char to the BC register
        
        add hl, bc
        ld (hl), $26              ;; Should now show an A on the screen roughly at xy chars
        jp OUT
        BUFFER:
                nop
                nop
        OUT:
                pop bc
                pop hl
        #endasm
        return 0;
    }
In previous versions of z88dk, I was able to get the parameters sent to the function as each value is pushed onto the stack. I have tried various methods of getting this but without success. If I send a 16-bit word to a function, how would I retrieve that value and pass it to a register pair?

I know the logic of the printAt function is fine because when I hard-code a value into BC, I get the letter A at approximately the correct place.

Thanks in advance,

Shaun
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

ShaunB wrote:When I do on the latest build, I am unable to use anything that I've defined, for instance, say my zx80.h file has this:

Code: Select all

    #define SCRLOC(X,Y)        ((Y<<5)+X)
    #define INVERSE(A)        (0x80 | A)
    #define EOL                        0x76
    #define EOF                        0xff
when I use SCRLOC in any function in my main.c file, I get an error telling me that SCRLOC is not defined even though I've included <zx80.h> in that file. This is no biggie, as I can simply copy paste those pre-processor symbols over. But this was working in previous version (like in the API thing I was developing here).
There is a difference between angle brackets in includes and quotes in includes:

#include <zx80.h>
means look for this header in the system directories.

#include "zx80.h"
means look for this header in the user's current working directory.

So if zx80.h is a header in your local directory, you should be using: #include "zx80.h"

This could be your problem but since we have a related issue being solved now:

https://github.com/z88dk/z88dk/issues/316

I am not sure if it is. The issue is concerned with the user defining headers of the same name as system headers, in which case the compiler is preferring the user header even when angle brackets are used.
However, let's say I have something like this:

Code: Select all

    #include <stdio.h>
    #define SCRLOC(X,Y)        ((Y<<5)+X)
    
    int main();
    int printAt(unsigned char xy);

    int main()
    {
        unsigned char cursorPos = SCRLOC(8,8);
        printAt(cursorPos);
        gets();
        return 0;
    }
    
    printAt(unsigned char xy)
    {
        #asm
        push hl
        push bc
        ld bc, $0000
        ld hl, $0004
        add hl, sp               ;; HL should now be pointing at my unsigned char
        ld (BUFFER), hl        ;; Store the value of our unsigned char in the BUFFER
        ld hl, ($400c)          ;; Get the screen pointer
        inc hl                     ;; We don't care about the first screen location as this
                                     ;; should always be a NEW LINE in ZX80 terms

        ld bc, (BUFFER)       ;; Write the value of the unsigned char to the BC register
        
        add hl, bc
        ld (hl), $26              ;; Should now show an A on the screen roughly at xy chars
        jp OUT
        BUFFER:
                nop
                nop
        OUT:
                pop bc
                pop hl
        #endasm
        return 0;
    }
In previous versions of z88dk, I was able to get the parameters sent to the function as each value is pushed onto the stack. I have tried various methods of getting this but without success. If I send a 16-bit word to a function, how would I retrieve that value and pass it to a register pair?
I'm pretty sure you're using sccz80 as compiler (no "-compiler=sdcc" on the compile line).

The compiler will push parameters in left to right order and then call the function. So if you are passing one char, the stack will contains 2 bytes for the char and 2 bytes for the return address. So the offset of the char will be sp+2.

In your code above however, you are pushing things on the stack before calculating where that parameter is. So after the "push hl, push bc" the stack contains: 2 bytes for char xy, 2 bytes for return, 2 bytes for hl, 2 bytes for bc. This means char xy is located at offset sp+6

One other thing is that the last #endasm is going to close the function so that a ret is inserted there by the compiler. The return value is in a subset of DEHL so for a 16-bit int return value you can just load HL with the result. If you're going to return a value, the printat function should really be declared as returning int -- the absence of a return type is relying on some old C to default to int.
ShaunB
Member
Posts: 41
Joined: Mon Jan 02, 2012 6:13 pm

Post by ShaunB »

alvin wrote:
ShaunB wrote:

Code: Select all

    #include <stdio.h>
    #define SCRLOC(X,Y)        ((Y<<5)+X)
    
    int main();
    int printAt(unsigned char xy);

    int main()
    {
        unsigned char cursorPos = SCRLOC(8,8);
        printAt(cursorPos);
        gets();
        return 0;
    }
    
    printAt(unsigned char xy)
    {
        #asm
        push hl
        push bc
        ld bc, $0000
        ld hl, $0004
        add hl, sp               ;; HL should now be pointing at my unsigned char
        ld (BUFFER), hl        ;; Store the value of our unsigned char in the BUFFER
        ld hl, ($400c)          ;; Get the screen pointer
        inc hl                     ;; We don't care about the first screen location as this
                                     ;; should always be a NEW LINE in ZX80 terms

        ld bc, (BUFFER)       ;; Write the value of the unsigned char to the BC register
        
        add hl, bc
        ld (hl), $26              ;; Should now show an A on the screen roughly at xy chars
        jp OUT
        BUFFER:
                nop
                nop
        OUT:
                pop bc
                pop hl
        #endasm
        return 0;
    }
In previous versions of z88dk, I was able to get the parameters sent to the function as each value is pushed onto the stack. I have tried various methods of getting this but without success. If I send a 16-bit word to a function, how would I retrieve that value and pass it to a register pair?
I'm pretty sure you're using sccz80 as compiler (no "-compiler=sdcc" on the compile line).

The compiler will push parameters in left to right order and then call the function. So if you are passing one char, the stack will contains 2 bytes for the char and 2 bytes for the return address. So the offset of the char will be sp+2.

In your code above however, you are pushing things on the stack before calculating where that parameter is. So after the "push hl, push bc" the stack contains: 2 bytes for char xy, 2 bytes for return, 2 bytes for hl, 2 bytes for bc. This means char xy is located at offset sp+6
Thanks, I have been using various methods to try to get at the 16 bit word (assuming that short is 16-bit as I previously understood it). So while the logic is sound, I still need a way to retrieve the value from the stack, something which I've not worked out yet. In previous versions, I found it safest to EXX before running custom assembly in a function and EXX before returning.
alvin wrote:One other thing is that the last #endasm is going to close the function so that a ret is inserted there by the compiler. The return value is in a subset of DEHL so for a 16-bit int return value you can just load HL with the result. If you're going to return a value, the printat function should really be declared as returning int -- the absence of a return type is relying on some old C to default to int.
Yes the code example wasn't literal; as you can see, I defined my printAt function as type int above, in my actual source it is an int.

Regards,

Shaun.
ShaunB
Member
Posts: 41
Joined: Mon Jan 02, 2012 6:13 pm

Post by ShaunB »

Hi Alvin,

This is the message when I include "zx80.h":

Code: Select all

cpp: line 2, Fatal error: Cannot open include file "zx80.h"
#include "zx80.h"
I assume this is the same as the issue that you highlighted.

Thanks,

Shaun.
ShaunB
Member
Posts: 41
Joined: Mon Jan 02, 2012 6:13 pm

Post by ShaunB »

Hi again Alvin,

I've amended the function as follows:

Code: Select all

        int printAt(unsigned short xy)
        {
                #asm
                exx
                pop bc
                ld hl,($400c)        ;; Get the screen pointer
                inc hl
                add hl,bc
                ld (hl),$26
                exx
                #endasm
        }
I am expecting BC to be my xy value, let's say 0x0010. If I hard-code the BC value as follows:

Code: Select all

        int printAt(unsigned short xy)
        {
                #asm
                exx
                ld bc,$0010
                ld hl,($400c)        ;; Get the screen pointer
                inc hl
                add hl,bc
                ld (hl),$26
                exx
                #endasm
        }
it works.

So, here's the question - is an unsigned short a 16-bit word? And therefore how do I get at the value xy and pass it to the BC register pair?

I am compiling from Windows PowerShell with:

Code: Select all

.\zcc.exe +zx80 -create-app "D:\8bit\z88dk\projects\zx80\sunday-league-football\main.c"
Thanks,

Shaun.
Timmy
Well known member
Posts: 179
Joined: Sat Mar 10, 2012 4:18 pm

Post by Timmy »

https://www.z88dk.org/wiki/doku.php?id=usage:stackframe

Of course this is only applicable on the old classic compiler.
ShaunB
Member
Posts: 41
Joined: Mon Jan 02, 2012 6:13 pm

Post by ShaunB »

Thanks Timmy,

I have tried the method described in the link.

Which version should I be using for the 'old classic compiler'?

Also I assume that there is no way to access the parameters sent to a function in the new non-classic compiler?

Regards,

Shaun.
ShaunB
Member
Posts: 41
Joined: Mon Jan 02, 2012 6:13 pm

Post by ShaunB »

Okay I have this working now, printAt(SCRLOC(x,y)); will produce an A at the right locations for the ZX80:

Code: Select all

        #define SCRLOC(X,Y)        (((Y<<5)+Y)+X)
        
        int main();
        unsigned short printAt(unsigned short xy);
        
        char c;
        unsigned char text[32];
        
        int main()
        {
                // codez ...
                for(c=12;c>=0;c--)
                {
                        printAt(SCRLOC(c,c));
                }
                gets(text);
        }

        unsigned char __FASTCALL__ printAt(unsigned short xy)
        {
                #asm
                push bc
                ld b,h
                ld c,l
                ld hl,($400c)
                inc hl
                add hl,bc
                ld (hl),$26
                pop bc
                #endasm
        }
However, I'd like to send a second parameter to the printAt() function, so I have:

Code: Select all

        unsigned short printAt(unsigned short xy, char c);
so I can output that char c after calculating the screen position. Obviously I'm unable to use the __FASTCALL__ in this instance as. Is there a work-around to this? i.e., could I have in my printAt() function something like:

Code: Select all

        unsigned char __FASTCALL__ printAt(unsigned short xy)
        {
                unsigned char _c = c;                // char c is set elsewhere and is defined global
                                                        // so that I may set it before calling this function
                #asm
                push bc
                ld b,h
                ld c,l
                ld hl,($400c)
                inc hl
                add hl,bc
                ;; Some way to calculate where my _c char lives in memory above
                ;; calculate it and pass that to the E register (as an example)
                ld (hl),e        ;; This now gets the char to output by some magic
                pop bc
                #endasm
        }
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

For the first version that took a char as input, you can get the parameter like this:

Code: Select all

// stack will be: 2 bytes xy, 2 bytes ret

unsigned char *printAt(unsigned char xy)
{
   #asm

   pop af    // return address
   pop bc   // bc = unsigned char xy
   push bc
   push af

   ...

   #endasm
}
Or:

Code: Select all

// stack will be: 2 bytes xy, 2 bytes ret

unsigned char *printAt(unsigned char xy)
{
   #asm

   ld hl,2
   add hl,sp

   ld c,(hl)   // c = lowest byte of xy
   ld b,h      // b = 0

   ...

   #endasm
}
I am a little wary of using EXX with the zx80 and zx81 because I'm not sure what registers are reserved for the display but maybe you know better about that.

For this, where xy has been made 16-bits, the stack is the same but the top 8 bits of xy are now meaningful, ie not necessarily 0. An 8-bit xy was probably inadequate as that can only do offsets from 0-255.

In z88dk, char=8, int=16, long=32, short=int. If you want to be specific about bit widths you can include <stdint.h> and use uint8_t, uint16_t, uint32_t, etc.

Code: Select all

// stack will be: 2 bytes xy, 2 bytes ret

unsigned char *printAt(unsigned short xy)
{
   #asm

   pop af
   pop bc    // bc = xy
   push bc
   push af

   ...

   #endasm
}
Or:

Code: Select all

// stack will be: 2 bytes xy, 2 bytes ret

unsigned char *printAt(unsigned short xy)
{
   #asm

   ld hl,2
   add hl,sp

   ld c,(hl)
   inc hl
   ld b,(hl)    // bc = xy

   ...

   #endasm
}
The saving of registers is not really necessary with sccz80 because it allows you to use whatever you want. The are restrictions specific to the zx80 and zx81 because the actual display system uses some of those registers however.

The change to fastcall causes the compiler to pass a single parameter via register. The "__FASTCALL__" thing you are using is deprecated in favour of "__z88dk_fastcall" because the latter is also compatible with the other compiler, zsdcc.

Code: Select all

unsigned char *printAt(unsigned short xy) __z88dk_fastcall
{
   #asm

   ; hl = xy

   ld bc,($400c)
   add hl,bc
   inc hl
   ld (hl),$26

   #endasm
}
However, I'd like to send a second parameter to the printAt() function, so I have:

unsigned short printAt(unsigned short xy, char c);
With a second parameter, more items are on the stack. sccz80 pushes in left to right order so:

Code: Select all

// stack = 2 bytes for xy, 2 bytes for c, 2 bytes for ret

unsigned short printAt(unsigned short xy, char c)
{
   #asm

   pop af    // return address
   pop de   // de = c
   pop hl   // hl = xy
   push hl
   push de
   push af

   ...

   #endasm
}
Or:

Code: Select all

// stack = 2 bytes for xy, 2 bytes for c, 2 bytes for ret

unsigned short printAt(unsigned short xy, char c)
{
   #asm

   ld hl,2
   add hl,sp

   ld e,(hl)    // c = char c
   inc hl
   inc hl
   ld a,(hl)
   inc hl
   ld h,(hl)
   ld l,a       // hl = xy

   ...

   #endasm
}
With more than one parameter you can't use fastcall linkage but you can use callee. In callee, the asm function clears the params from the stack:

Code: Select all

// stack = 2 bytes for xy, 2 bytes for c, 2 bytes for ret

unsigned short printAt(unsigned short xy, char c) __z88dk_callee
{
   #asm

   pop hl   ; return address
   pop de  ; de = char c
   ex (sp),hl   ; hl = xy, return address written

   ...

   #endasm
}
Global variables have a fixed memory location so you can refer to them by name after adding a leading underscore:

Code: Select all

unsigned int someVar;

void func(void)
{
   #asm

   ld hl,(_someVar)   ;; someVar is 16-bits
   ...

   #endasm
}
If you're project becomes larger and there are a lot of asm functions, it's better to put those functions in a separate asm file to keep the c looking cleaner and more portable.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

As far as compilers are concerned, there are two: sccz80 and zsdcc. You're using sccz80 and I think you have to use sccz80 for the zx80 and zx81 targets because sdcc will fool around with the index registers which is probably not compatible with the display stuff.
ShaunB
Member
Posts: 41
Joined: Mon Jan 02, 2012 6:13 pm

Post by ShaunB »

Thanks for the top tips Alvin. I'll amend my sub routines now.

Regarding EXX - this has never caused an issue with the ZX81 or ZX80 - in fact, I found more issues with not restoring the registers before returning from my sub-routines within the C functions and EXX was the most memory efficient way of handling it.

As for which compiler, for the ZX80 development, I don't need an animated screen as it's going to be a Football Management type simulator but I'll heed your warnings about which compiler to use.

Many thanks,

Shaun.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

I also forgot to mention that #asm, #endasm are also deprecated - I'm too used to seeing it :)

They should be replaced with "__asm" and "__endasm;" - note the semicolon on endasm. Again it's for compatibility with zsdcc. I haven't been able to get zsdcpp to accept the #asm syntax yet.
ShaunB
Member
Posts: 41
Joined: Mon Jan 02, 2012 6:13 pm

Post by ShaunB »

My 'work-around' printAt() routine can be viewed here for those interested:

https://github.com/sbebbers/sunday-league-football

The fast printAt() should be portable to the ZX81, though you have to either convert each ASCII char in C to the ZX80/ZX81 character equivalent, or use the pre-processor symbols as I have. A bit of a pain but means fast output where needed.

Regards,

Shaun.
ShaunB
Member
Posts: 41
Joined: Mon Jan 02, 2012 6:13 pm

Post by ShaunB »

Hi,

Consider the following example:

Code: Select all

#include <stdio.h>

void main();

signed long money;

void main()
{
        for(money = 0; money < 512; money++)
        {
                printf("%d, ", money);
        }
        gets();
}
The output is zero (whether the money scalar value is signed or unsigned).

If I change money to an unsigned char then it won't work. If I change money to a signed char then it will.

So, how would I output a signed or unsigned long using printf(); for ZX80 development? Is this possible?

Thanks,

Shaun.
User avatar
dom
Well known member
Posts: 1310
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

It looks like zx80 has got the full printf code so %ld is available.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

There's an error there because "%d" is for 16-bit numbers but money is 32-bit (long). %d will only see the upper 16-bits of money and that will be zero.

If you want to print 32-bits you'll have to use "%ld". I'm not sure what goes on with the zx80/zx81 targets because 32-bit math in z88dk uses the exx set and one index register. Maybe it's ok?

money as a char is only 8-bit so as an unsigned char can hold 0-255 and as a signed char -128 to 127. You should see things printed but not the number 0-512 in order.
ShaunB
Member
Posts: 41
Joined: Mon Jan 02, 2012 6:13 pm

Post by ShaunB »

Code: Select all

    printf("%ld",125000);
This works fine on my ZX80 program.

Thanks for the tip.

Shaun.
ShaunB
Member
Posts: 41
Joined: Mon Jan 02, 2012 6:13 pm

Post by ShaunB »

Something very nice in z88dk is this sort of thing:

Code: Select all

    while(myString[y++] != 0x76){}
The value of y is therefore the next line. Although x = y = 0; costs more bytes in compilation.

Regards,

Shaun.
ShaunB
Member
Posts: 41
Joined: Mon Jan 02, 2012 6:13 pm

Post by ShaunB »

Hi,

Another question; rand() and srand() have predictable results. Is there a way to make the random number generation more random?

Thanks,

Shaun.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

You need a source of entropy. A lot of people use the amount of time it takes for a person to press a key at a menu or something.

This code snippet shows the idea (these functions come from newlib but you can do similar in classic):

Code: Select all

        //Wait for Keypress and Randomize

        in_wait_nokey();
        for (counter = 31416; !in_test_key(); counter += 10061) ;
        srand(counter);
The loop just spins around until a key is pressed. Inside the loop a 16-bit unsigned int gets 10061 added to it in each loop step. That number should be relatively prime to 65536 so that counter will take on every number in 0...65535 in the loop.
ShaunB
Member
Posts: 41
Joined: Mon Jan 02, 2012 6:13 pm

Post by ShaunB »

Hi,

Is there an easy way to get at memory? ~0x401e is where the frame rate is. If that location is available to C stdio then I could use that for my srand() value. This would surely be slightly different per game.

Thanks,

Shaun.
ShaunB
Member
Posts: 41
Joined: Mon Jan 02, 2012 6:13 pm

Post by ShaunB »

Correction: frame rate counter*
ShaunB
Member
Posts: 41
Joined: Mon Jan 02, 2012 6:13 pm

Post by ShaunB »

Actually, this test works:

Code: Select all

        unsigned char frameCounter()
        {
                __asm
                ld a,(0x401e)
                __endasm;
        }
I'll test it with my game code now.

Regards,

Shaun.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

You can read an int from a specific address like this:

Code: Select all

srand(*(unsigned int *)(0x401e));
There are problems with emulators because quite often you can double click on a program to launch it, in which case FRAMES will contain the same value each time the program is started that way.
ShaunB
Member
Posts: 41
Joined: Mon Jan 02, 2012 6:13 pm

Post by ShaunB »

Hi Alvin,

I think that all ZX81 memory expansions work on the ZX80, and you are able to emulate the ZX80 with 32K RAM expansion. I'm therefore wondering if it's possible to access the 2nd 16K page with z88dk somehow? Is this possible? Or is this a question for ZX80 experts?

Thanks,

Shaun
Post Reply