terça-feira, novembro 26, 2024

Simulador de K7 para o ZX81/TK82C - Parte 2

Apenas uma atualização rápida neste projeto: a geração dos pulsos agora está sendo feita usando a PIO (Programmable Input/Output) do RP2040.


A referência principal para a programação foi este livro aqui: https://leanpub.com/rp2040 😉

O primeiro passo foi escolher o clock da PIO. Este clock define o tempo que cada instrução demora para executar (o tempo de um ciclo). Cada instrução requer um ciclo, mais um tempo adicional (delay) que você pode adicionar. Um detalhe importante é que cada instrução possui cinco bits que são compartilhados entre o valor de delay e o controle (opcional) de pinos (o chamado side-set).

No meu caso eu usei um 1 bit de side-set para controlar o pino que gera o sinal, restando quatro bits para o delay. Portanto o tempo máximo para execução de uma instrução é de 16 ciclos (1 da instrução mais 15 de delay).

Para quem não lembra da parte anterior, um bit "0" gera 4 pulsos (150us em nível ALTO e 150us em nível BAIXO); um bit 1 gera 9 pulsos (com os mesmos tempos). Nos dois casos, após os pulsos tem 1300us de nível BAIXO.

Portanto, os tempos que eu preciso gerar são 150us e 1300useg. Usando um clock de 50us, os tempos curtos correspondem a 3 ciclos e o longo a 26 ciclos.

A passagem de dados do processador para uma máquina de estado da PIO é feito através de uma fila. A instrução OUT extrai "n" bits do primeiro da fila. A cada "m" bits extraídos a "fila anda" (se estiver com o modo autopull ligado). Se a fila estiver vazia quando o OUT é executado, o programa para (stall) até algo ser colocado na fila. No meu caso, "n" é 1 e "m" é 8: OUT vai pegar um bit de cada vez e pegar outro dado na fila a cada 8 bits.

O programa completo da PIO está aqui:

.program k7
.side_set 1

.wrap_target

GET_BIT:
  OUT X,1             SIDE 1   [7]  ; 8 silence
  SET Y,4             SIDE 1   [15] ; 16 silence
  JMP !X, PULSE_TEST  SIDE 1        ; 1 silence
  SET Y,8             SIDE 1        ; 1 silence
PULSE:
  NOP                 SIDE 0   [2]  ; 3 on
  NOP                 SIDE 1   [1]  ; 2 off
PULSE_TEST:
  JMP Y--, PULSE     SIDE 1        ; 1 off
  
.wrap

O programa usa os dois registradores auxiliares X e Y. X é usado para guardar o bit a transmitir e Y contar os pulsos. A instrução "JMP !X, PULSE_TEST" desvia para PULSE_TEST se X for zero. A instrução "JMP Y--, PULSE" primeira testa Y e depois o decrementa; se Y era diferente de zero, desvia para PULSE.

O .wrap no final faz com que o programa retorne automaticamente para o ponto marcado com .wrap_target, economizando um JMP. No total são apenas 8 instruções (cada uma das duas PIOs do RP2040 possui uma memória para até 32 instruções).

A versão que está aí foi o resultado de algumas iterações de otimização feitas "no papel". Para minha surpresa e alegria, o programa da PIO funcionou de primeira.

Aonde eu precisei fazer um acerto foi no programa C que interage com a PIO. Como os bits precisam ser tratados do mais significativo para o menos, a instrução OUT foi configurada para fazer deslocamento para a esquerda do dado obtido da fila. Portanto o que interessa são os 8 bits mais significativos do que foi escrito na fila. Como cada item da fila tem 32 bits, é preciso deslocar o byte a enviar 24 bits para a esquerda, o que eu tinha esquecido.

Um teste com osciloscópio confirmou que os tempos e a quantidade de pulsos estavam corretos. A carga de um programa fixo no ZX81 ocorreu sem problemas.

Os programas estão no meu github: https://github.com/dquadros/PicoK7

Agora é hora de tratar o cartão SD.

Nenhum comentário: