Operation of Z80 interrupt mode 0 in the CPC+ design

The basic steps for Z80 interrupt mode 0 are:

  1. Device issues a interrupt request to the Z80
  2. If maskable interrupts are enabled: If maskable interrupts are disabled:

This interrupt mode can't be used reliably on the CPC. The 8-bit interrupt-vector read by the Z80 will normally be &FF, but this is not guaranteed if expansion peripherals are connected. (When &FF is read, the equivalent of a "RST 38H" instruction is executed, and interrupt mode 0 will act the same as interrupt mode 1). Therefore, because the 8-bit interrupt-vector is not always the same it is not advisable to use this interrupt mode on the CPC.

The CPC+ hardware can generate a 8-bit interrupt-vector, and this is normally used with Z80 Interrupt mode 2. The 8-bit interrupt-vector depends on the source of the interrupt (raster interrupt or a interrupt from a DMA channel). The 8-bit interrupt vector is reliable and can be predicted, and as a result the CPC+ can use the Z80 interrupt mode 0.

However, the number of instructions that can be executed in Z80 interrupt mode 0 on the CPC+ is limited, because there is a limited set of numbers that can be defined by the CPC+ 8-bit interrupt vector.

The 8-bit interrupt vector is constructed as follows:

(See "Arnold V" Specification, Issue 1.4, section 2.7 and Extra CPC+ information for more details)

As a result a limited set of instructions can be executed.

I used the following test programs and observed the results:

;; program 1
org &8000

;; disable interrupts
di

;; unlock asic to gain access to asic registers
ld b,&bc
ld hl,sequence
ld e,17
.seq 
ld a,(hl)
out (c),a
inc hl
dec e
jr nz,seq

;; page-in asic registers
ld bc,&7fb8
out (c),c

;; set z80 interrupt mode 0
im 0

;; set 8-bit interrupt vector (bits 7..3).
;; bit 0 = automatically clear interrupts
ld a,0
ld (&6805),a

;; page-out asic registers
ld bc,&7fa8
out (c),c

;; enable interrupts
ei
;; wait for next interrupt
halt        ;; the halt instruction will wait for a interrupt-request. When this is received, the
            ;; program counter is advanced to the next instruction before the interrupt is executed.
            ;; So after "halt" has been executed, the program counter will be pointing
            ;; to the "LD D,E" instruction.
;; a "dummy" instruction
ld d,e


di          ;; disable interrupts so we can change interrupt mode safely
im 1        ;; set Z80 interrupt mode 1 (see comment for "BRK" below)
brk         ;; this is a special instruction provided by the Maxam assembler/dissassembler/monitor
            ;; when executed a dump of the register values is displayed. This instruction assembles
            ;; to a RST 30H. This instruction uses the Amstrad firmware to execute, and the Amstrad
            ;; firmware requires interrupt mode 1.

;; this is the sequence to unlock the ASIC extra features
.sequence
defb &ff,&00,&ff,&77,&b3,&51,&a8,&d4,&62,&39,&9c,&46,&2b,&15,&8a,&cd,&ee

In the code above, the only interrupts that will be active will be the standard raster interrupts which occur approximatly every 52 scan-lines (the other interrupt sources must be explicitly enabled and triggered for an interrupt to occur from these sources). When a raster interrupt has been signalled, the ASIC will provide a 8-bit interrupt-vector with bits 2..0 set to 6. In the program I have set the remaining bits to 0, so the complete 8-bit interrupt-vector will be &06, which is the equivalent of the "LD B,n" instruction. ("LD B,n" is a multi-byte opcode consisting of two bytes. The first byte is the opcode, and the second byte is the value of "n". After this instruction has been executed B will have the value defined by "n")

Before this program was executed B has the value &7F, but after this program had executed B had the value &53. &53 is the opcode for the "LD D,E" instruction.

I found that if I changed the "LD D,E" instruction to another instruction, the value of B would always be the same as the first opcode-byte of the replacement instruction. I also found that the instruction following the "HALT" was always executed, therefore the program counter was not incremented when the additional opcode bytes were fetched.

Therefore the second byte of the "LD B,n" instruction is always being loaded from the current program counter location.

Using program 1, I could use any opcode where bits 2..0 are "6".

To use other instructions, I would need to enable another interrupt source:

;; program 2
org &8000

;; disable interrupts
di

;; unlock asic to gain access to asic registers
ld b,&bc
ld hl,sequence
ld e,17
.seq 
ld a,(hl)
out (c),a
inc hl
dec e
jr nz,seq

;; page-in asic registers
ld bc,&7fb8
out (c),c

;; wait for end of vsync
;; I use this so I can predict when the next raster interrupt will occur
;; In the CPC and CPC+ design a raster interrupt is triggered on the second HSYNC after the
;; the start of the VSYNC signal.
ld b,&f5
.l1
in a,(c)
rra
jr nc,l1
.l2
in a,(c)
rra
jr c,l2

;; set interrupt mode 0
im 0

ld hl,&4030     ;; dma instruction "issue interrupt request and stop executing dma list"
ld (&9000),hl  

ld hl,&9000
ld (&6c04),hl   ;; set dma channel 1 address (source of dma instruction list for channel 1)
ld a,0
ld (&6c06),a    ;; set dma channel 1 prescalar

ld a,&20
ld (&6805),a    ;; set interrupt vector

;; clear raster counter (has the effect of forcing next raster interrupt to not
;; occur closer than 52-HSYNCs). 
ld bc,&7f00+%10011100
out (c),c

ld a,%00000010  ;; enable dma channel 1, first instruction will execute on the next HSYNC
ld (&6c0f),a

;; page-out asic registers
ld bc,&7fa0
out (c),c

;; enable interrupts
ei
;; wait for interrupt to occur
halt
;; "dummy" instructions
ld d,h
ld e,l


di          ;; disable interrupts so we can change interrupt mode safely
im 1        ;; set Z80 interrupt mode 1 (see comment for "BRK" below)
brk         ;; this is a special instruction provided by the Maxam assembler/dissassembler/monitor
            ;; when executed a dump of the register values is displayed. This instruction assembles
            ;; to a RST 30H. This instruction uses the Amstrad firmware to execute, and the Amstrad
            ;; firmware requires interrupt mode 1.

;; this is the sequence to unlock the ASIC extra features
.sequence
defb &ff,&00,&ff,&77,&b3,&51,&a8,&d4,&62,&39,&9c,&46,&2b,&15,&8a,&cd,&ee

This program sets up a simple dma list with a single instruction. The DMA instruction triggers a interrupt and stops the execution of the list. Before the DMA list is executed, the raster counter is reset, this forces the next raster interrupt to occur 52 lines later. As a result the DMA interrupt is serviced before the raster interrupt. DMA channel 1 is used, and bits 2..0 of the 8-bit vector will be set to &02. I have setup bits 7..3 to &20, making the final 8-bit interrupt-vector &22. &22 is the "LD (nnnn),HL" instruction. (The "LD (nnnn),HL" is a three byte opcode. The second and third byte define "nnnn" and is the memory address at which HL is written.).

After this program was executed I found that HL had been written to address &5454. &54 is the opcode for the "LD D,H" instruction. Since bits 7..0 and bits 15..8 of the address were the same, the same byte must have been read twice. This implies that the program counter was not incremented for any of the opcode byte fetches. I also found that DE matched the value of HL, therefore the "LD D,H" and "LD E,L" instructions were executed.

I found that if I changed the "LD D,H" instruction, the address was always generated from the first opcode-byte of the replacement instruction. Therefore the opcode bytes were fetched from the current program counter location.

From these experiments I found the following:

I have not yet tested the following:

Confirmation of the function of the IM x instructions

As a result of being able to use interrupt mode 0 with the CPC+, I was able to confirm the action of the following unofficial instructions:

Opcode Action
ED 4E same as IM 0
ED 6E same as IM 0

The following program was used to confirm these instructions:

;; program 3
org &8000

;; disable interrupts
di

;; unlock asic to gain access to asic registers
ld b,&bc
ld hl,sequence
ld e,17
.seq 
ld a,(hl)
out (c),a
inc hl
dec e
jr nz,seq

;; setup for interrupt mode 1
ld a,&c3
ld hl,im1_interrupt_handler
ld (&0038),a
ld (&0039),hl

;; setup for interrupt mode 2
;; initialise bits 15..8 of interrupt vector
ld a,&40
ld i,a

;; setup interrupt handler jumpblock
ld ix,&4000
ld hl,im2_interrupt_handler
ld b,0
.sim2
ld (ix+0),l
ld (ix+1),h
inc ix
inc ix
djnz sim2

;; disable interrupts
di
;; set interrupt mode
;; {insert instruction here}
defb &ed,&4e

;; enable interrupts
ei

;; wait for interrupt
halt
;; (this is used by interrupt mode 0)
nop

di          ;; disable interrupts so we can change interrupt mode safely
im 1        ;; set Z80 interrupt mode 1 (see comment for "BRK" below)
brk         ;; this is a special instruction provided by the Maxam assembler/dissassembler/monitor
            ;; when executed a dump of the register values is displayed. This instruction assembles
            ;; to a RST 30H. This instruction uses the Amstrad firmware to execute, and the Amstrad
            ;; firmware requires interrupt mode 1.

.im1_interrupt_handler
ld b,1
ei
ret

.im2_interrupt_handler
ld b,2
ei
ret

;; this is the sequence to unlock the ASIC extra features
.sequence
defb &ff,&00,&ff,&77,&b3,&51,&a8,&d4,&62,&39,&9c,&46,&2b,&15,&8a,&cd,&ee

I tested the above program with the official instructions: "IM 0", "IM 1" and "IM 2".

Therefore from observing the value of B after the program had executed I could determine the function of the instructions.

Notes

  1. The CPC+ does not support the full operation of Z80 interrupt mode 0. The CPC+ will only generate a single 8-bit interrupt-vector and will ignore additional opcode fetches for multi-byte instructions.

    So, the operation of multi-byte instructions may be different with hardware designs that fully support interrupt mode 0.

    Any signals that may be generated by the Z80 to fetch extra opcode bytes will be ignore. As a result, the observations I have seen may only apply to the CPC+ hardware design.

  2. As far as I know Z80 interrupt mode 0 has not been used in any programs. If it has been used in a CPC program, then it is likely to be unreliable when expansion peripherals are attached. If "FF" is read as the interrupt vector then this will have the same effect as interrupt mode 1. However, this will never work on the CPC+. When CPC+ features have not been enabled, the 8-bit interrupt vector will be "00" and not "FF" causing a "NOP" instruction to be executed. If CPC+ features have been enabled, and only standard interrupts are active, then a "LD B,n" instruction will be executed. It is not possible to program the CPC+ so that a 8-bit interrupt vector of "FF" is generated.