Dissecando a ULA do TK90X - Parte 9



O GERADOR DO DISPLAY


Chegamos finalmente à última e talvez a parte mais esperada deste artigo: como a ULA realmente gera a imagem para o circuito de video? Vamos começar pelo geração do sincronismo. Alguns dos sinais necessários, nós já vimos rapidamente na parte 3 e na parte 4 deste artigo, que seriam HBlank, VSyncEn, VSync e HSync.


Aqui é bem simples, como pode ser visto. Uma porta NAND de U27 combina HBlank com VSyncEn para formar o Blank. Isso quer dizer que, se estamos fora do período de imagem, o Blank vai estar ativo, informando que o sinal de video para o monitor deve ser "cortado". Por exemplo, durante os pulsos de sincronismo ou retraço, somente os pinos de sincronismo do monitor devem receber sinais, enquanto que os pinos de cores devem ficar em nível baixo.

HSync e VSync são combinados no AND U26 e formam o Sincronismo composto, que é um dos pinos externos da ULA, já para o circuito de video.

No VHDL, já vimos os códigos que geram os sinais HSync e Vsync, então o único que faltou foi o Sincronismo composto, gerado pela linha abaixo:
   
    CSYNC  <= HSync_n and VSync_n;

Agora precisamos gerar os sinais auxiliares Vout e OutLatch. O Vout informa se a posição do contador na tela está dentro do miolo (Vout = 0) ou se está numa das quatro bordas, superior, inferior, esquerda ou direita (Vout = 1). Já o Outlatch funciona como um pulso, informando que o próximo byte do vídeo já está posicionado para o circuito que desenhará os pixels na tela.




O Vout é gerado pela combinação de VBorder com HC8 ou seja, o DEn, sinal que vimos na parte 6. Se DEn = 1, estamos na borda, ou o contador HC8 já passou do miolo. Vout é apenas o mesmo DEn, atrasado pelos FFs de U7. Já o Outlatch é a combinação de HC2..0 com o próprio Vout nas portas de U9, U10 e U11. Num resumo, Outlatch acontece a cada 8 pulsos do Clock de 7Mhz, caso estejamos dento do miolo (Vout = 0)

No VHDL o Vout é gerado ligeiramente diferente, mas a idéia continua sendo a mesma do esquema. Ele está em nível baixo quando estamos no miolo.

    -- Geracao do Vout ( Mudanca entre borda e "miolo" )
	-- Se Vout = 0, estamos dentro da tela
    process ( VC, hc )
    begin
        if ( vc( 7 ) = '1' and vc( 6 ) = '1' ) or vc( 8 ) = '1' then     -- Borda superior/inferior
        
            Vout <= '1';
            
        elsif ( hc >= "000001011" and hc < "100001100" ) then -- dentro do miolo
        
            Vout <= '0';
            
        else -- Borda lateral
        
            Vout <= '1';
            
        end if;
    end process;
	
    -- Geramos o sinal de Vout com atraso
    process ( OSC )
    begin
        if rising_edge( OSC ) then
        
            Vout_delayed <= Vout;
            
        end if;
    end process;
	
    -- SLoad (OutLatch no esquema) - O bits dos pixels devem ser enviados a tela um a um. 
    -- Este sinal avisa quando o próximo byte esta pronto pra ser enviado, e é gerado a cada 8 ciclos de clock, quando estamos dentro do miolo	
    process( clk7 )
    begin
        if falling_edge( clk7 ) then
        
            if ( hc( 2 ) = '1' and hc( 1 ) = '0' and hc( 0 ) = '0' and Vout = '0' ) then            
            
                SLoad <= '1';
                
            else
            
                SLoad <= '0';
                
            end if;
            
        end if;
    end process;


Continuando no esquema, temos agora onde os bytes são lidos a partir das memórias DRAM e armazenados internamente pela ULA.


Os latchs U19 e U21, que na prática são uma memória de 1 byte, estão ligados diretamente no barramento de dados externo e são controlados pelos sinais AL1 e AL2 que já vimos anteriormente. Quando AL1 sobe, indicando um valor para os pixels da tela, U21 armazena o barramento e disponibiliza para U22 (já falaremos dele). Quando AL2 sobe, indicando um valor de atributo, U19 armazena. Se temos um pulso de OutLatch, o byte de atributos é passado para saída de outro latch, o U20, que serve diretamente os bits de atributo para a próxima etapa do gerador de video. U22 é um conversor paralelo/serial, ou seja, recebe em suas entradas um byte completo e vai disponibilizando bit a bit conforme os pulsos de clock, nesse caso, 7Mhz. Então, conforme dito acima, OutLatch pulsa a cada 8 ciclos de clock de 7Mhz, cada byte tem 8 bits, logo, OutLach é sincronizado sempre no começo de cada byte para a área de pixels e quando se passarem 8 pulsos de clock, ou seja, todos os bits desse byte foram servidos para a próxima etapa, novamente é recebido outro pulso de OutLatch, logo, um novo byte para o conversor paralelo/serial.

No VHDL, temos a variável SRegister que faria o papel de U22. O que acontece aqui é que a cada pulso de clock, fazemos um deslocamento dos bits e mais a frente no código, somente o bit 7 dele é levado em conta. A cada 8 pulsos de clock, SRegister é inicializado novamente, com um novo valor de byte da área de vídeo.

    -- Buffer para os Pixels - Fazemos uma cópia do barramento de dados para usar posteriormente durante o envio para a saida RGB
    process( AL1 )
    begin
        if rising_edge( AL1 ) then
        
            BitmapReg <= ULA_D;
            
        end if;
    end process;
	
    -- Buffer para os Atributos - Fazemos uma cópia do barramento de dados para usar posteriormente durante o envio para a saida RGB
    process( AL2 )
    begin
        if rising_edge( AL2 ) then
        
            AttrReg <= ULA_D;
            
        end if;
    end process;

    -- Shift - Pega o byte (que pode ser um pixel ou um atributo) e empurra um bit (shift) para a esquerda.
    process( clk7 )
    begin
        if rising_edge( clk7 ) then
        
            if ( SLoad = '1' ) then   -- Sload é o OutLatch do esquema
            
                SRegister <= BitmapReg;
            else
            
                SRegister <= SRegister( 6 downto 0 ) & '0';
                
            end if;
            
        end if;
    end process;


Com os pixels na posição para serem colocados na tela, ainda existe uma verificação extra. Se o atributo de "flash" está ligado, temos que inverter a cor de fundo com a cor da frente.


O contador U23 é incrementado a cada pulso de sincronismo vertical e nesta configuração mudará o estado da entrada AND de U26 a cada 16 pulsos de sincronismo. Se temos o atributo flash ligado (nível alto), o porta XOR de U18 inverterá a sua saída, na prática trocando a cor de fundo pela cor da frente, na próxima etapa do gerador de vídeo.

Em VHDL não temos nenhuma surpresa e acontece exatamente como o descrito. Note que na variável de saida "Pixel", usamos somente o bit 7 do SRegister, conforme falamos no passo anterior.

   -- Contador do Flash. Usado para calcular a velocidade que pisca 
    process( VSync_n )
    begin
        if falling_edge( VSync_n ) then
        
            FlashCnt <= FlashCnt + 1;
            
        end if;
    end process;
    
	
    -- Testa se o byte lido é "paper" (Pixel=0) ou "ink" (Pixel=1). 
    -- Somente o bit 7 (mais a esquerda) é colocado na tela. Os próximos aguardam o shift acontecer 
    -- Notar que o FlashCnt inverte a condição quando é a hora de piscar
    Pixel <= SRegister( 7 ) xor ( AttrOut( 7 ) and FlashCnt( 5 ) );


E finalmente, onde o vídeo é colocado nos pinos de saída da ULA.


Os CIs U24 e U25 são um duplo seletor 4 para 1 e dependo da seleção feita nas por A e B, temos umas das quatro entradas selecionadas em uma saída, como cada um deles é duplo, na pratica temos oito entradas e oito saidas. Conforme vimos, o sinal Vout fica em nível alto quando nas bordas e nível baixo quando no miolo e notem que ele vai ligado na entrada B dos seletores. Então quando Vout = 1 as entradas 3 e 4 dos seletores são direcionadas para as suas respectivas saídas. Vejam que nelas estão ligados os bits de cores da bordas B2, B1, B0, que foram armazenadas em um latch pela porta 254, de acordo com a parte 2 deste artigo. Então, se Vout for nível alto, nas saídas teremos sempre os bits de cor da borda.

Por outro lado, se Vout for nível baixo, estamos no miolo e a seleção depende da porta A dos seletores, que é controlada pelo streaming constantes de bits de vídeo vindo da etapa anterior. Se o bit for 0, selecionamos as primeiras portas dos seletores, que são os bits do PAPER (DD3, DD4, DD5), a cor de fundo. Se for 1, selecionamos os bits do INK (DD0, DD1, DD2), a cor da frente. Já o bit do Bright é ligado diretamente nas duas portas, então sempre será repassado para a porta de saída, independente do fluxo de bits.

As portas AND U45:D, U49:A e U49:B ficam ligadas diretamente nos pinos de saída da ULA e são esses pinos de fato que levam a informação de vídeo para o resto do TK. Aqui temos um controle extra, exercido pelo NOT U29. Se estamos no período de blanking, ou seja, o monitor está recebendo os sinais de controle e não uma imagem, as saídas são forçadas para baixo, o que seria o equivalente a cor preta. Isso é feito para aumentar a compatibilidade com os monitores, porque muitos "não gostam" de receber dados junto com os sinais de controle, enquanto outros funcionam normalmente.

As portas OR de U48 detectam se as três saídas de cor são nível baixo, ou seja, o preto. Se a cor de saída é preta, U49:D, que também é ligado a um pino de saída da ULA, fica em nível baixo, desativando o bright para o preto, porém repassando o sinal de bright vindo de U49:C para qualquer outra cor. Um detalhe aqui é que o equivalente dessas portas OR U48 não estão presentes na ULA original porque durante o funcionamento normal do micro, o próprio LM1886 faz essa "limpeza" do bright na cor preta antes de jogar para saída de vídeo do micro. Elas foram adicionadas no clone para colocar um monitor de 15Khz diretamente nas saídas RGB da ULA, tendo assim um resultado mais agradável aos olhos.

No VHDL:

    -- Colocarmos as informações nas variáveis de RGB
    process( HBlank_n, VBlank_n, Pixel, AttrOut )
    begin
        if ( HBlank_n = '1' and VBlank_n = '1' ) then
		
            if ( Pixel = '1' ) then -- Se é ink
            
                rI <= AttrOut( 6 );
                rG <= AttrOut( 2 );
                rR <= AttrOut( 1 );
                rB <= AttrOut( 0 );
                
            else -- se é paper
            
                rI <= AttrOut( 6 );
                rG <= AttrOut( 5 );
                rR <= AttrOut( 4 );
                rB <= AttrOut( 3 );
                
            end if;
        else -- esta fora da tela (periodos de "blank"), então, preto
        
            rI <= '0';
            rG <= '0';
            rR <= '0';
            rB <= '0';
            
        end if;
    end process;
		
    -- Saída de vídeo
    RED    <= rR;
    GREEN  <= rG;
    BLUE   <= rB;
    BRIGHT <= rI and ( rR or rg or rB );   -- Saída de Bright. Temos que combinar com os bits de cor para evitar bright no preto
	


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.