z88dk / z88dk

The development kit for over a hundred z80 family machines - c compiler, assembler, linker, libraries.
https://www.z88dk.org
Other
907 stars 171 forks source link

sccz80: introduce compiler intrinsics for i/o port addressing ? #78

Closed aralbrec closed 7 years ago

aralbrec commented 7 years ago

sdcc provides "special function registers" (sfr) as a means to communicate to the compiler access to special features of the target cpu. For the z80/z180 sfrs are used to inform the compiler about port i/o mappings.

The syntax for declaring an 8-bit port is:

__sfr __at 0x1f IO8; // declare an 8-bit port at i/o address 0x1f

Then the program can perform input or output by reading or writing "IO8" as if it were an unsigned char.

value = IO8;    // in a,(IO8) ; ld (_value),a
IO8 = value;   // ld a,(_value); out (IO8),a

(In z180 mode 8-bit i/o is performed using the z180 instructions IN0 and OUT0 which always place 0 in the top 16 bits of a port address).

The syntax for declaring a 16-bit i/o port is:

__sfr __banked __at 0xfbfe IO16; // declare a 16-bit port at i/o address 0xfbfe

The program performs input or output by reading or writing to "IO16" as if it were an unsigned char:

value = IO16;    // ld a,_IO16/256 ; in a,(_IO16 & 0xFF)
IO16 = value;   // ld a,(_value) ; ld bc,_IO16 ; out (c),a

The overhead of a call to a library subroutine that does port i/o as is done in the sccz80 library currently is very expensive in relation to the execution time of these compiler intrinsics.

Would it be possible to add this sort of feature to sccz80?

I also dislike sdcc's __sfr syntax for declaring i/o ports. They have a bunch of cpus to worry about where the naming and syntax makes more sense but for the z80 family I think the declaration can be done better. I was already thinking about changing the declaration for sdcc by having zpragma doing a translation.

Section 6 of the Embedded Extensions TR may have something to say about port declarations.

suborb commented 7 years ago

I think something may be possible. Any thoughts on syntax?

It will be much easier to have some builtin functions that can be easily inlined by the compiler, I've just knocked up this:

 int     a;
 int main()
{
     __builtin_out16(10,a);
     __builtin_out16(10,3);
     __builtin_out16(a, 100);
}

To generate this (raw compiler output, I can fix the push/pop issue):

    ld      hl,(_a)
    ld      bc,#10
    ld      a,l
    out     (c),a
    ld      bc,#10
    ld      a,3
    out     (c),a
    ld      hl,(_a)
    push    hl
    pop     bc
    ld      a,100
    out     (c),a
    ret
suborb commented 7 years ago

I've just added in support for the same syntax.

A simple test looks like it generates the right code for the two flavours.

aralbrec commented 7 years ago

Nice I will move the new c lib toward using this.

I still hate that syntax but getting the behaviour right is more important than offending my sensibilities. Also I haven't been hit by a thunderbolt that tells me a better way.

aralbrec commented 7 years ago

For the z180 can we make sure the INO and OUT0 instructions are used for 8 bit i/o?

suborb commented 7 years ago

I can do, but I don't think sdcc is doing that.

aralbrec commented 7 years ago

sdcc selects IN0/OUT0 via compile line switch "-portmode=z180" or via pragma "#pragma portmode z180".

In zcc we're doing "-portmode=z180" so zsdcc z180 compiles will always use IN0/OUT0 for 8-bit i/o.

In sdcc they've put the pragma in z180.h so that all z180 compiles will also use IN0/OUT0 since you have to include this file to get port definitions for the z180. It's not as robust as what we've done but it is their intention.

On the z180 I think you always have to have it enabled since the z180's hardware registers are all mapped in 8-bit but will only respond if the high byte is 0 which is what IN0/OUT0 do. If these instructions are not used, you may end up doing port i/o without the high byte being zero and the on-chip peripherals won't respond.

suborb commented 7 years ago

I've merged but there may be a few rough spots about the place.

For instance you can do this:

_sfr __at 0xde port;

c = *port;

Which generates this:

    in      a,(_bob)
    ld      l,a
    ld      h,0
    call    l_gint  ;

Whereas sdcc generates an error.

suborb commented 7 years ago

This feature is present, going to close off.