terça-feira, 12 de novembro de 2019

Interrupts no Z80A/ZX Spectrum

Neste artigo, falaremos dos interrupts do Z80A, mas do ponto de vista do ZX Spectrum, já que a arquitectura de hardware, e a ROM têm influência no comportamento destas funcionalidades.

Um interrupt, pelo menos ao nível do Z80, é basicamente a recepção de um sinal externo em que a CPU, recebendo e aceitando, interrompe o que está fazer, guarda o endereço de execução no stack, e salta para uma "subrotina".


O processador Z80 tem dois tipos de interrupts: não mascaráveis (NMI) e mascaráveis.

As NMI (Non-Maskable Interrupts) são interrupts que não podem ser ignoradas, e chamam a subrotina em $0066; a NMI não funciona sem hardware adicional, devido a um bug na ROM. São activadas com o pino NMI do Z80.


RESET
0066
PUSH AF
Save the current values held in these registers.

0067
PUSH HL

0068
LD HL,($5CB0)
The two bytes of NMIADD must both be zero for the reset to occur.

006B
LD A,H

006C
OR L

006D
JR NZ,$0070
Note: this should have been 'JR Z'!

006F
JP (HL)
Jump to START.
NO_RESET
0070
POP HL
Restore the current values to these registers and return.

0071
POP AF

0072
RETN

A interrupção não-mascarável BUSRQ não tem utilidade na arquitectura do ZX Spectrum e raramente é mencionada.

As mascaráveis são activadas 50 vezes por segundo, com a ULA a enviar um sinal ao pino INTR do Z80.  Note-se que o envio deste sinal coincide no ZX Spectrum 48K com o inicio do varrimento do ecrã pela ULA.

As interrupções mascaráveis podem ser ignoradas pelo software fazendo DI anteriormente e EI de novo para as aceitar. Têm três modos de funcionamento no Z80:
  • IM0
  • IM1
  • IM2
O modo IM0, é compatível com o Intel 8080, executando os opcodes presentes no bus de dados, e é raramente, se nunca, usado no ambiente ZX Spectrum. Dado ao desenho de hardware (bus em descanso em $FF), normalmente, o modo IM0 porta-se como o modo IM1.

Em IM0 e IM1, chegando um interrupt, é equivalente a um RST $38. Isto executa a rotina da ROM em $0038, MASK_INT, que incrementa variáveis de contar tempo (FRAMES), e chama a rotina da ROM de varrimento de teclado.


MASK_INT
0038
PUSH AF
Save the current values held in these registers.

0039
PUSH HL

003A
LD HL,($5C78)
The lower two bytes of the frame counter (FRAMES) are incremented every 20 ms. (U.K.) The highest byte of the frame counter is only incremented when the value of the lower two bytes is zero.

003D
INC HL

003E
LD ($5C78),HL

0041
LD A,H

0042
OR L

0043
JR NZ,KEY_INT

0045
INC (IY+$40)
KEY_INT
0048
PUSH BC
Save the current values held in these registers.

0049
PUSH DE

004A
CALL KEYBOARD
Now scan the keyboard.

004D
POP DE
Restore the values.

004E
POP BC

004F
POP HL

0050
POP AF

0051
EI
The maskable interrupt is enabled before returning.

0052
RET

Também podemos activar o modo IM2 por software. Neste modo, o registo I tem de ser previamente carregado com um valor. Como por defeito, o bus de Spectrum em modo descanso sem nenhum hardware tem o valor $FF, sendo activado uma interrupt, o endereço da rotina de tratamento desta interrupt vai ser obtido de dois bytes (uma word), em RAM apontada por I no byte alto e $FF no byte baixo.

por exemplo:

LD A,$F9
LD A,I
IM  2
RET

tendo em $F9FF o valor $F800, a rotina de interrupts em modo dois vai estar em $F800.

ORG $F800

:---- rotina pretendida
JP $0038

Tópicos "avançados"

Internamente ao Z80 existem dois flip-flops que guardam e são afectados pelos estados dos interrupts. São conhecidos por IFF1 (interrupt switch) e IFF2 (copy). A tabela de estados deles é esta:


O byte menos significativo associado ao registo I no processamento da interrupt IM2 nem sempre é $FF na presença de outros periféricos extras no bus do Spectrum. Jogos comerciais que usam o IM2 normalmente têm isso em conta. Criam buffers com 256 bytes com o mesmo valor, ou apontam para uma área da ROM que tem os mesmos valores. Um truque conhecido, é apontar o I para a zona ROM "SPARE" que a partir de $386E até $3CFF é preenchida com $FF; por exemplo, I a $39, e sendo assim o apontador para IM2 $FFFF e $0000.  $0000 vai ter sempre o byte $F3.

O registo IY deve estar no valor original se se invocar rotinas da ROM (a ROM espera ter o IY com o valor $5C3A).

O Z80 toma cuidados para não acontecer um interrupt imediatamente a seguir a um DI (se for NMI), ou imediatamente a seguir a um EI. A única excepção a esta regra de poder acontecer um interrupt na instrução imediata a EI, é ter uma instrução HLT, que espera por um interrupt.

Um evento de recepção de interrupts, desliga as interrupts e incrementa o registo R.

Veja Interrupt Behaviour of the Z80 CPU  para mais detalhes.

Nota adicional: o Z80 também tem um pino de sinal WAIT, que estende o corrente ciclo de bus para alem de 1 ciclo T. É destinado a ser usado por periféricos. No caso do desenho da ULA do Spectrum, preferiram optar por não o usar para controlar o Z80.

Sem comentários:

Enviar um comentário