Questions about IM2 setup on ZX Spectrum

ZX80, ZX 81, ZX Spectrum, TS2068 and other clones
Post Reply
Stefan123
Member
Posts: 85
Joined: Fri Oct 21, 2016 7:57 am

Questions about IM2 setup on ZX Spectrum

Post by Stefan123 »

The standard way to set up an IM2 interrupt service routine in z88dk on ZX Spectrum seems to be in the following way:

Code: Select all

intrinsic_di();
im2_init((void *) 0xD000);
memset((void *) 0xD000, 0xD1, 257);
z80_bpoke(0xD1D1, 0xC3);
z80_wpoke(0xD1D2, (uint16_t) my_isr);
intrinsic_ei();
Would it be possible to replace the memset() and pokes with im2_install_isr() as in the following way or similar:

Code: Select all

intrinsic_di();
im2_init((void *) 0xD000);
im2_install_isr(0, (void *) my_isr);
intrinsic_ei();
Are there any other more elegant ways of setting up an IM2 isr without using pokes?

Code example 1 at https://www.z88dk.org/wiki/doku.php?id= ... interrupts seems to indicate that the first snippet above is the only way to set up an IM2 isr on ZX Spectrum. Is that correct?

How do you safely allocate the space needed by the 257-byte interrupt vector table? The only example I can find in z88dk that demonstrates setting up an IM2 isr is the SP1 demo program. This program configures the stack to grow downwards from address 0xD000 (using the REGISTER_SP pragma) and places the interrupt vector table immediately after the stack. Does SP1 require the stack to be located at address 0xD000? If you don't use the SP1 sprite package and/or have the stack at its default location at the top of memory (is that its default location?), where do you then put the interrupt vector table and have z88dk aware of its placement when linking so it is not overlapping with the code/data/bss sections of your program?
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Are there any other more elegant ways of setting up an IM2 isr without using pokes?
Not really except for generating some code for that address and loading that there as part of your basic loader. You have control over exact placement in memory of sections so you could create a section ORGed at 0xd000 that contains the im2 table and has your isr in it. It will be output as a separate binary by z80asm, you can create a tap file for that and load it to the right address inside your basic loader.
Code example 1 at https://www.z88dk.org/wiki/doku.php?id= ? interrupts seems to indicate that the first snippet above is the only way to set up an IM2 isr on ZX Spectrum. Is that correct?
Every method is going to be similar to this. You need an im2 table and you need an interrupt routine at the jump destination of the table or else a jump to your isr at that address.

Some people may try to rely on specific contents of the 48k's rom as it contains a block of 0xff bytes that you can point the "I" register into so that the z80 will jump to 0xffff on interrupt. Then they poke a "jr" there so that the z80 reads jr at 0xffff and 243 from rom address 0. That's a jr -13 so you can have a jump to your interrupt routine at address 0xffff-13. That means no additional 257 byte im2 table. But I personally don't like to rely on that. You can try it if you like though. You will have to poke some stuff at the top of memory to get the jr and the jp to your isr or you could memcpy it from an array.
How do you safely allocate the space needed by the 257-byte interrupt vector table?
The memory map is completely under your control. You have to decide what goes where and you can decide what is safe.

I am assuming it's a newlib compile here:

The default zx compile is building a binary at address 32768 that grows upward. The stack is being placed just under the UDGs (65368?) and interrupts are disabled. All of this is customizable with pragmas. Line 18 of this file ( https://github.com/z88dk/z88dk/blob/mas ... ig.inc#L18 ) lists the crt defaults that you can override. The meanings of those defaults can be found here ( https://www.z88dk.org/wiki/doku.php?id= ... figuration ). So, for example, to change ORG away from 32768, change the value of CRT_ORG_CODE to something else via pragma in your .c, or pragma-define on compile line, or in a separate pragamas file that is read during compile (explained by the last link).

You can go further though by defining your own memory map to put different things in different places. I'll stop talking there, but suffice to say you can break your program into pieces that are spread into different memory banks or different regions in memory.

Long story short, you decide what the memory map is.
The only example I can find in z88dk that demonstrates setting up an IM2 isr is the SP1 demo program. This program configures the stack to grow downwards from address 0xD000 (using the REGISTER_SP pragma) and places the interrupt vector table immediately after the stack. Does SP1 require the stack to be located at address 0xD000? If you don't use the SP1 sprite package and/or have the stack at its default location at the top of memory (is that its default location?), where do you then put the interrupt vector table and have z88dk aware of its placement when linking so it is not overlapping with the code/data/bss sections of your program?
sp = 0xd000 fits well with the memory map defined by the default build of sp1. sp1 reserves some memory at the top of ram to manage sprites, hold rotation tables, etc. Its configuration is controlled by this file ( https://github.com/z88dk/z88dk/blob/mas ... fig_sp1.m4 ) which also contains a memory map at the end with suggestions for the locations of im2 table and the stack. Some discussion about changing sp1's memory map can be found here ( https://www.z88dk.org/wiki/doku.php?id= ... es:sp1_ex1 )

So the numbers for the sp1 example are not random - they are being selected to fit with the memory map defined by sp1. You can actually put things anywhere you want as long as it doesn't overlap sp1's structures or your program.

If you're not using sp1, you have more freedom where things go. The binary produced by z88dk is self-contained by default except for the stack and heap. So you just arrange to put these things like stack & im2 table out the way of the resulting binary. The heap, by default, will be initialized to fill the space between the end of the program and the bottom of the stack. If you don't want that, look into the CLIB_MALLOC_HEAP_SIZE pragma (link above).

I know this is a little bit complicated information but the gist is you decide the memory map and where things will go,
Timmy
Well known member
Posts: 393
Joined: Sat Mar 10, 2012 4:18 pm

Post by Timmy »

Stefan123 wrote:Would it be possible to replace the memset() and pokes with im2_install_isr() as in the following way or similar:

Code: Select all

intrinsic_di();
im2_init((void *) 0xD000);
im2_install_isr(0, (void *) my_isr);
intrinsic_ei();
Are there any other more elegant ways of setting up an IM2 isr without using pokes?
No. Setting up interrupts on the Spectrum is a very intricate and precise process. If you hide it, you end up having a lot of problems with it. Everyone would be wondering how many bytes it costs, and where it is, and where you installed the isr jump routine, and why there is need to install it (you could just have poked a EI; RETI instead of a jump, too).

Here's a link if you want to know how interrupts work: https://archive.org/stream/sinclair-use ... 9/mode/2up
How do you safely allocate the space needed by the 257-byte interrupt vector table?
You do this by writing down exactly which space you need it and tell/hope the rest of the game doesnt reach there at all.
The only example I can find in z88dk that demonstrates setting up an IM2 isr is the SP1 demo program. This program configures the stack to grow downwards from address 0xD000 (using the REGISTER_SP pragma) and places the interrupt vector table immediately after the stack. Does SP1 require the stack to be located at address 0xD000? If you don't use the SP1 sprite package and/or have the stack at its default location at the top of memory (is that its default location?), where do you then put the interrupt vector table and have z88dk aware of its placement when linking so it is not overlapping with the code/data/bss sections of your program?
Lots of questions, so lots of answers:

1. No, 0xD000 is just a random number. Sometimes I put my stack at 24000, for example.

2. With Future Looter (that's a z88dk game without using sp1), the stack was put at 65530.

while the interrupt was at:

Code: Select all

        im2_Init(0xfe00);
        memset(0xfe00, 0xfd, 257);
3. If you want to make 128k games, it's advisable to put both the interrupt table and the stack a lot lower than 0xD000 to... say, 48000. (But that's a much longer story for another day. Also I never did this so I can't really say how easy this is.)

4. You don't always need a complicated isr, the example at https://github.com/z88dk/z88dk/blob/mas ... mo2/demo.c has this:

Code: Select all

   z80_bpoke(0xd1d1, 0xfb);    // POKE instructions at address 0xd1d1 (interrupt service routine entry)
   z80_bpoke(0xd1d2, 0xed);
   z80_bpoke(0xd1d3, 0x4d);    // instructions for EI; RETI
5. I would definitely recommend setting up an interrupt for sp1 like I described in (4). I find sp1 doesn't always work perfectly if you didn't use an interrupt. You don't need something special in there, in fact I'd suggest you to make the isr as simple as possible if you're just new with interrupt programming.
User avatar
dom
Well known member
Posts: 2091
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

There's an im2 example using the classic library at examples/spectrum/preempt.c
Stefan123
Member
Posts: 85
Joined: Fri Oct 21, 2016 7:57 am

Post by Stefan123 »

Thanks for all informative replies :-)

I think that the approach described by Bob and Alvin on the Spectrum Next users' forum at https://www.specnext.com/forum/viewtopi ... 1&start=50 is a sound one, where you place the interrupt vector table at 0x8000 before your program and sets CRT_ORG_CODE to 0x8184 or higher. In that way, you get the interrupt vector table out of the way.

I liked Timmy's link to the Sinclair User article on interrupt handling, I got a bit nostalgic when reading it :-) I had almost all issues of Sinclair User from 1982 to 1988 but they got lost when I moved a couple of years ago :-(
Post Reply