Uma parte importante da ULA é a sua interação com a CPU. Nos ZX-Spectrum compatíveis, incluindo ai o TK90X e TK95,
temos os primeiros 16Kb da memória RAM compartilhados entre a ULA e a CPU.
A ULA lê continuamente essa faixa de endereços como memória de vídeo para desenhar o display gráfico,
e esta tem preferência sobre a CPU que é simplesmente parada, suspendendo-se o clock.
A "contenção" da CPU ocorre quando a ULA precisa fazer a leitura dos dados do display e atributos de cores,
mas por algum motivo o Z80 também quer acessar a mesma faixa de endereços para suas operações de leitura e escrita.
Vamos notar que essa contenção ocorre apenas dentro do "miolo" da tela e nunca durante o desenho da borda ou dos períodos de blanking.
Para facilitar, vamos dividir a explicação em três grupos:
OS SINAIS AUXILIARES
Um sinal que não falamos durante a parte 4 foi o sinal VBorder, que nada mais é do que um indicador se estamos desenhando o miolo ou as bordas.
Note que no esquema ele é formado pelos bits 6, 7 e 8 do contador vertical.
U14 é uma porta AND, que assume nivel alto quando os bits 7 e 6 estão alto, logo, 11000000b = 192 e junto com o bit 8 combinado com o OR de U17, teriamos 111000000b = 448.
Como vimos anteriormente, esse contador reseta em 261 ou 311, dependo da seleção de 50Hz ou 60Hz,
então na prática temos o sinal de VBorder ativo entre 192 e a última contagem, 261/311, dependendo do caso.
Esse valores podem ser compreendidos rapidamente, se levarmos em conta o modo de como os contadores formam a imagem, conforme já explicado na parte 3.
No esquema vemos ainda o sinal DEn: se estamos na borda (VBorder) ou o contador horizontal está a partir de 256 (fora do miolo), DEn será nivel alto.
No VHDL temos o código similar, porém o que seria o VBorder e o DEn foram combinados numa única variável já com a lógica invertida para facilitar a sintaxe do código em outras partes.
Enquanto no esquema temos os sinais ativos em nível alto, no código ele é gerado como ativo em nível baixo.
-- Sinal de Border/DEn (1 quando esta desenhando um pixel do "miolo", 0 quando desenha um pixel da borda)
process( clk7 )
begin
if falling_edge( clk7 ) then
if ( ( vc( 7 ) = '1' and vc( 6 ) = '1' ) or
vc( 8 ) = '1' or
hc( 8 ) = '1'
) then
Border_n <= '0';
else
Border_n <= '1';
end if;
end if;
end process;
O PERÍODO DE CONTENÇÃO
Primeiramente fazemos a seleção de uma faixa de valores no contador horizontal. Para isso utilizamos um multiplexador 3 para 8,
um 74138 em U6, que somente faz a seleção quando o sinal DEn está em nivel baixo, ou seja, dentro do "miolo" da tela.
A porta AND de U31 faz a combinação das portas 0 e 7 do multiplexador,
que logo depois é invertido por um NOT em U29 e combinado com o DEn no OR U30.
O FF em U28 gera o sinal de sync wait que é passado para o resto do circuito de contenção,
no próximo ciclo de clock 7Mhz.
Bem, trocando em miúdos, se o DEn estiver em nivel alto, nunca teremos um sync wait, o que faz sentido,
já que se estamos desenhando a borda, a CPU pode estar liberada para o processamento.
Se DEn for baixo, dependeremos de U6, que se estiver selecionando C0 (hc3..1 = 000) ou C7 (hc3..1 = 111), não gerará um wait,
porém gerando se estivermos entre as seleções de C1 a C6.
Analogamente, no VHDL temos o mesmo tipo de teste do contador, levando também em conta a variável Border_n que como dito acima embloga o VBorder e o DEn.
process ( CLK7 )
begin
if falling_edge( CLK7 ) then
if Border_n = '1' and ( hc( 3 downto 0 ) >= "0011" and hc( 3 downto 0 ) <= "1110" ) then
WaitSignal <= '0';
else
WaitSignal <= '1';
end if;
end if;
end process;
Um particularidade nesse bloco: como no esquema o sync wait é gerado "atrasado" um ciclo de clock por causa do FF,
no código testamos também o bit 0 do HC, acrescentando uma unidade no contador. No lugar de testarmos HC3..1 maior ou igual a 0010b que seria o nosso C1 do esquema,
testamos HC3..0 maior ou igual a 0011b, que seria o mesmo C1, mas no próximo ciclo de clock.
O CONTROLE DE WAIT
Nesta parte do controle de Wait,
temos que verificar se houve um acesso na porta da ULA ou se a CPU está tentando usar a memória RAM baixa,
os primeiros 16kb que são compartilhados com a ULA, na faixa de 16384 e 32767.
Uma das portas OR de U30 combina o sinal invertido da linha de endereço externa A14 e A15. Logo, se A14 = 1 e A15 = 0, estamos na RAM baixa.
O AND U31 combina esses sinais com o pino de /CS (acesso à porta 254). Para didática do artigo, vamos chamar este bloquinho de "contenção 1".
Outra porta do AND U31 combina os sinais /CS e /MREQ, que são atrasados em um ciclo de clock pelo FF U28. Vamos chamar esse de "contenção 2".
Outras duas portas OR de U30 combinam os três sinais, "wait", "contenção 1" e "contenção 2".
Se todos os três sinais forem em nível baixo, a ULA está em estado de contenção, porque ela precisa fazer um acesso à memória e tem preferência sobre a CPU,
já que o sinal de vídeo não pode parar.
O FF U46 usa o bit 0 do contador horizontal na sua porta de dados, que como vimos pulsa a 7Mhz.
Pela própria natureza do FF, ele funciona nessa posição como um divisor de clock, então sua saída pulsa continuamente a 3.5Mhz.
Aqui aproveitamos para extrair o sinal do pino 39, o Subcarrier, usado externamente no TK e nada mais é do que o clock contínuo de 3.5Mhz.
O sinal de contenção é invertido pelo NOT U29 e combinado com esse clock continuo em U17.
Logo, se a ULA está em estado de contenção, temos um nivel alto em U17, o que suspende o clock da CPU, em outro NOT de U29.
No VHDL acredito que ficou bem auto-explicativo com os comentários e o funcionamento passo a passo é exatamente o mesmo descrito acima para o esquema.
-- Para ficar mais claro no código, atribuimos o pino de acesso da ULA a uma variavel
ioreq_n <= CS;
-- Gera os sinais de IORQ e MREQ atrasados, necessarios para a verificação da contenção da CPU
process( CPUClk )
begin
if rising_edge( CPUClk ) then
ioreqtw3 <= ioreq_n;
mreqt23 <= MREQ;
end if;
end process;
cdet1 <= ( A15 or not A14 ) and ioreq_n; -- =0 se A15 for baixo e A14 alto (endereço 16384 a 32767, memoria baixa)
ou se houve um acesso na porta 254 da ULA
cdet2 <= not ( ioreqtw3 and mreqt23 ); -- =0 quando ioreqtw3(IOREQ atrasado) e mreqt23(MREQ atrasado) forem 1
CLKContention <= not ( WaitSignal or cdet1 or cdet2 ); -- =1 (contenção) quando todos os sinais forem 0
process( OSC )
begin
if rising_edge( OSC ) then
-- Clock para a CPU.
-- Pulsa a 3.5 Mhz com hc(0), porem se existe contenção, ela fica em nivel alto.
CPUClk <= CLKContention or hc( 0 );
-- Gera o subcarrier. Aqui pegamos apenas o clock de 3.5Mhz, porque o subcarrier não pode parar.
subc <= hc( 0 );
end if;
end process;