Dissecando a ULA do TK90X - Parte 7



O /RAS E O /CAS


Vamos começar a falar sobre como a ULA acessa a memória RAM, mas é necessário uma breve explicação sobre o funcionamento de uma DRAM. Nos anos 80 existiam basicamente dois tipos de memórias RAM que equipavam os computadores da época, a SRAM (RAM estática) e a DRAM (RAM dinâmica). Ambas são memórias voláteis e perdem seu conteúdo na falta de alimentação, mas eletronicamente, na construção, são bem diferentes. A SRAM, caríssima na época, é construida com base em milhares de flip-flops que assumem um estado 0 ou 1. Basta então o CI acessar o FF responsável por uma posição de memória e ler/gravar o bit necessário. Já as DRAMs eram baratas e por esse motivo foram escolhidas por muitos fabricantes de micros, inclusive o ZX Spectrum e o TK90X. A grande diferença que para cada posição de memória temos um conjunto de transistor-capacitor internamente no chip. Então, cada capacitor recebe uma pequena voltagem e começa a descarregar-se. Para manter a informação da memória válida, é necessário que ela seja constantemente acessada (centenas de vezes por segundo) para o capacitor de cada posição de memória seja recarregado, o que chamos de refresh. Como já devem ter imaginado, é um sistema lento, mas no caso da CPU Z80 já vinha com toda a facilidade dos ciclos de refresh para memórias dinâmicas, o que já facilitava o projetista e barateava o custo final da máquina, porém, no entanto, a ULA tem seu próprio esquema de refresh, porque divide o barramento com a CPU.

Nesta primeira parte sobre a ULA e as memórias dinâmicas, vamos falar especificamente dos sinais /RAS e /CAS. Aliás, uma segunda peculiaridade das memórias dinâmicas é que estas tem o seu endereçamento em forma de matriz, com linhas e colunas, no lugar do endereçamento absoluto, usado pelas memórias estáticas. Um caso básico de uso, seria primeiro selecionar a linha da matriz onde queremos acessar, então abaixamos o sinal /RAS (Row Access Strobe). Em seguida selecionamos a coluna da matriz e abaixamos o sinal /CAS (Column Access Strobe). Desse modo, temos o dado daquela posição de memória presente na saída do CI.

No caso da ULA, conforme vimos na parte 6, temos uma constante disputa entre a ULA e a CPU pelo uso da memória. Enquanto a CPU é responsável pela escrita da memória baixa, a ULA fica com a leitura e ao mesmo tempo refresh conforme falaremos mais a frente. Vimos também que a ULA arbitra por meio de parada de clock da CPU, mas temos também um sinal auxiliar interno para facilitar o uso de algumas partes do circuito.


Nesta parte do esquema aparece novamente o multiplexador U6 para a contagem de ciclos Estou chamando aqui de ciclos, cada uma das iterações do clock interno de 3.5Mhz, que são zeradas a cada 8 ciclos. Ele é usado para dividir o tempo entre a leitura dos bytes da memória e o acesso da CPU ao barramento. Falamos superficialmente desse multiplexador na parte 6, enquanto estavamos vendo a parte de contenção, onde ele também é utilizado.

Aqui, o multiplexador U6 alimenta o FF U50, detectando os ciclos entre C3 e C7. Como a entrada de clock e dados está aterrada, a porta Q fica alta imediatamente que C3 abaixe, ficando neste estado até C7 abaixar, o que inverte a saida. Logo, C3, C4, C5 e C6, colocam VidBusEn em nível baixo, avisando que a ULA tem preferência, enquanto C0, C1, C2 e C7 colocam CPUBusEn em nível baixo, avisando que é a vez da CPU usar o barramento das memórias.

Ainda neste mesmo esquema, temos os sinais AL1, formados pela porta AND em U4 selecionando C3 e C5, enquanto o AL2 em outro AND seleciona C4 e C6. AL1 é um sinal que indica que é hora de pegar um byte da memória referente a área de pixels, enquanto AL2 indica a hora do byte de atributos (cores). Veremos mais detalhes sobre eles, quando falarmos da geração do vídeo.


Apesar de muitas portas envolvidas, a lógica é até simples. Vamos dividir e falar primeiro sobre os sinais vindos do Z80: se ele quer fazer um acesso a uma posição de memória, primeiro são posicionados as linhas de endereço de A0 a A15, então o sinal /MREQ abaixa, seguido dos sinais /RD ou /WR, dependendo se é uma operação de escrita ou leitura. No nosso caso, o multiplexador U41, abaixa o sinal RAM16, se A14 = 1, A15 = 0 e /MREQ = 0, ou seja, se a CPU requisitou um acesso a memória na faixa de 16384 a 32767. O estado de RAM16 é atrasado um ciclo de clock pelo FF U8, se o RAM16 for um nível baixo e aí temos o sinal cRAS (CPU RAS).

Já o AND U45 combina o /RD e /WR, unindo com o RAM16 em U40. Se o RAM16 for 0 e um dos sinais de controle /RD ou /WR forem 0, temos uma operação completa, precisando de um cCAS (CPU CAS), que é gerado também com um atraso de um ciclo de clock no FF de U42. Notem que enquanto o cRAS abaixa no segundo ciclo de clock (após o /MREQ), o cCAS abaixaria no terceiro (após o /RD ou /WR), dando tempo assim das DRAM registrarem a troca da matriz, que veremos mais a frente.

A porta OR U40 combina o CPUBusEn com o /WR, gerando o sinal /VRAMWR externo. Na prática somente a CPU pode escrever nas memórias, então é uma boa idéia manter a verificação pela variável auxiliar, para garantir que a memória não será corrompida.

No VHDL, é bem similar e dispensa maiores detalhes, além dos comentários.

    --Gera o sinal RAM16, quando a CPU quer fazer um acesso nos primeiros 16Kb de RAM que e controlado pela ULA
    ram16 <= '0' when MREQ = '0' and A14 = '1' and A15 = '0' else '1';
	
    -- Geracao do cRAS
    process ( OSC )
    begin
        
        if ( falling_edge( OSC )) then
        
            -- O RAS vindo da CPU e gerado quando existe um acesso nos primeiros 16KB de RAM, que e controlada pela ULA
            cRAS <= ram16; 
            
        end if;
    
    end process;
	
    -- Geracao do cCAS
    process ( OSC )
    begin
        if ( falling_edge( OSC )) then
        
            -- O CAS da CPU acontece quando ja existe cRAS e e definida uma operacao de leitura ou escrita
            cCAS <= ( WR and RD ) or cRAS;
            
        end if;
    end process;
	
    VRAM_WR  <= cpubus_en or WR; -- Se e a vez da CPU usar a memoria e se houve uma operacao de escrita, ativa o sinal.
    -- A ULA na pratica nunca escreve nas memorias dinamicas, apenas le o seu conteudo e monta a tela. 


Continuando no esquema, a parte da leitura da memória pela ULA é um pouco mais complicada, mas ao mesmo tempo é simples porque tem poucas portas lógicas envolvidas:

HC0 é o primeiro bit do contador horizontal. Como descrito na parte 1, o contador "anda" a 7Mhz, logo se pegarmos apenas o primeiro bit, ele estaria pulsando a 3.5MHz, funcionando ai como um divisor de clock. Justamente esse é o principio aqui, um clock contínuo de 3.5Mhz, no NAND U43, que vai combinado com AL1. A saída vai com o CPUBusEn em U44, gerando o pulso de RAS, que é atrasado um pulso de clock de 14Mhz em U42, gerando ai os sinais de vRAS (Video RAS) e o VidClock (clock de video) na saída invertida do FF.

Já o segundo sinal, combinamos o clock 3.5Mhz, invertido por uma porta NAND de U44 e combinado com o Clock de 7Mhz em outro NAND. A saída vai com o CPUBusEn novamente em U44, para formar o sinal de vCAS. Mas por que combinar o clock de 3.5Mhz junto com o 7Mhz? O que queremos aqui é encurtar o sinal de 3.5Mhz, fazendo ele subir antes do tempo, porque precisamos de um total de quatro pulsos rápidos do vCAS, ou seja, quatro leituras em sequência para alimentar as informações do circuito de vídeo. Veremos essa parte em detalhes quando mostrar como o video é desenhado.

Finalmente, duas portas AND em U45 combinam o vRAS e o cRAS para formar o nosso sinal externo /RAS e o vCAS com cCAS para formar o /CAS.

Talvez essa parte fique mais clara com uma ilustração:


Em VHDL temos exatamente a mesma lógica descrita acima.

    -- Geracao do vRAS
    process ( OSC )
    begin
        if ( falling_edge( OSC )) then
        
            vRAS <= cpubus_en nand (( hc( 0 ) ) nand AL1 );
            
        end if;
    end process;

    -- Geracao do vCAS
    vCAS <= cpubus_en nand ( not ( hc( 0 ) ) nand clk7 );

    -- Clock para o contador de Refresh das memorias dinamicas
    VidClock <= not ( vRAS );

    RAS <= cRAS and vRAS; -- Combina o cRAS e o vRAS para formar o RAS final 
    CAS <= cCAS and vCAS; -- Combina o cCAS e o vCAS para formar o CAS final
    


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.