Dissecando a ULA do TK90X - Parte 6



A INTERFACE ENTRE A ULA E O Z80


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;


Voltar para o índice




Dúvidas, sugestões? Use o espaço abaixo.


Voltar - Home


Comente



COMENTÁRIOS DESABILITADOS NO MOMENTO! RETORNAM EM BREVE
É expressamente proibido a reprodução total ou parcial deste texto sem a minha devida autorização por escrito.