Teach Remote lab lessons

Teach lesson

Lógica digital na DE1-SoC com VHDL (4/6): contadores e registradores de deslocamento opcionais

Construa em VHDL um contador com divisor de clock e, se houver tempo, explore extensões opcionais com BCD e registradores de deslocamento.

  • Altera DE1-SoC
  • 40 min
  • Graduação, introdutório
  • Português (Brasil)
  • Digital systems & FPGA
Altera DE1-SoC
Altera DE1-SoC

Learning Outcomes

  • Explicar a função de um divisor de clock e usá-lo para tornar visível o comportamento de 50 MHz.

  • Construir um contador de 4 bits e ler seu valor nos LEDs e em um display de sete segmentos.

  • Reconhecer contadores BCD e registradores de deslocamento como extensões opcionais.

  • Identificar problemas de largura, como uma constante que não cabe no registrador declarado.

Student activity preview

Activity Content

Preview only. In a class session, students can fill in responses and submit their work to the teacher.

1

Da memória ao movimento: por que precisamos de um divisor de clock

10 min

Na Lição 3, você construiu circuitos que guardam um bit ou um byte: um flip-flop e um registrador de carga paralela. Cada um atualiza o valor armazenado em uma borda de clock. Agora, você reutilizará essa ideia em um circuito que avança sozinho a cada borda: um contador que percorre 0, 1, 2, 3, .... Se houver tempo, uma extensão opcional usa o mesmo princípio em um registrador de deslocamento, que move os bits lateralmente.

O clock CLOCK_50 da placa tem 50 MHz, isto é, 50.000.000 de bordas de subida por segundo. Ligado diretamente a ele, um contador de 4 bits percorreria seus 16 valores cerca de três milhões de vezes por segundo. Os LEDs pareceriam um borrão estável, e a câmera remota não conseguiria mostrar cada passo.

Por isso usamos um divisor de clock. Ele conta as bordas de CLOCK_50 e, ao alcançar a contagem terminal DIV, alterna um sinal mais lento chamado slow_clk e começa de novo. Quanto maior DIV, mais lento é slow_clk. O circuito principal recebe slow_clk, não CLOCK_50, e passa a se mover em uma velocidade visível.

Projetos sequenciais precisam declarar o clock. O modelo blink.vhd não o inclui, portanto cada entidade desta lição adiciona CLOCK_50 : in std_logic. Os demais sinais são SW(9 downto 0) (chave para cima = 1), KEY(3 downto 0) (ativo em nível baixo: pressionado = 0), LEDR(9 downto 0) (ativo em nível alto) e HEX0HEX5 (sete segmentos ativos em nível baixo).

Divisor de clock que recebe CLOCK_50 a 50 MHz, conta até 33.333.333 e alterna slow_clk, produzindo aproximadamente 0,75 Hz e uma borda de subida a cada 1,33 segundo.

O divisor conta bordas rápidas de CLOCK_50 e só alterna slow_clk quando chega a DIV. Aumentar DIV reduz a frequência; assim, o circuito principal se move devagar o suficiente para o olho e a câmera.

Um contador de 4 bits ligado diretamente a CLOCK_50 avança cerca de três milhões de vezes por segundo, enquanto a câmera captura poucas imagens por segundo. O que você veria nos LEDs, e por quê?

Se você dobrasse DIV, de 33.333.333 para 66.666.666, como mudaria aproximadamente a taxa de avanço do contador, e por quê? Responda em uma frase.

2

Exercício 2-C: contador de 4 bits com divisor de clock

12 min

Um contador é um registrador cujo próximo valor é o atual mais um. Com 4 bits, ele guarda 0 a 15; depois de 15, volta automaticamente a 0, pois 15 + 1 = 10000 em binário e apenas os quatro bits inferiores (0000) cabem no registrador.

O projeto tem duas partes. O divisor conta bordas de CLOCK_50 até DIV e alterna slow_clk. O contador soma um em cada borda de subida de slow_clk; KEY(0) o reinicia em 0. A contagem aparece em binário em LEDR(3 downto 0) e como um dígito hexadecimal em HEX0: 09 e depois AF para 1015.

Temporização. Com DIV = 33.333.333, slow_clk alterna depois de cerca de 0,67 s. Um passo só ocorre em uma borda de subida, e uma onda quadrada precisa de duas alternâncias para completar um período. Portanto, o contador avança aproximadamente a cada 1,33 s, ou cerca de 0,75 Hz.

Cuidado com a largura. O registrador do divisor tem 27 bits. Um registrador de 26 bits só chega a 2^26 − 1 = 67.108.863, então DIV = 100.000.000 não caberia nele. Com 27 bits, o máximo é 2^27 − 1 = 134.217.727; assim, tanto 100.000.000 quanto 8.333.333 cabem. Sempre confirme se uma constante cabe na largura declarada.

Uma ressalva importante. Usar diretamente um slow_clk dividido é um atalho didático para tornar o movimento visível. Em projetos de produção, normalmente se mantém tudo em CLOCK_50 e se usa um clock enable, um pulso de um ciclo que autoriza a atualização. Isso permite uma análise temporal mais segura pelas ferramentas de síntese.

-- Código inicial do estudante: preencha as linhas TODO antes de sintetizar.
-- Lição 4 - Exercício 2-C: contador de 4 bits com divisor de clock.
-- KEY(0)=reset. LEDR(3 downto 0)=contagem; HEX0=contagem hexadecimal.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity blink is
    port (
        CLOCK_50 : in  std_logic;
        KEY      : in  std_logic_vector(3 downto 0);
        LEDR     : out std_logic_vector(9 downto 0);
        HEX0     : out std_logic_vector(6 downto 0)
    );
end entity blink;

architecture rtl of blink is
    constant DIV : unsigned(26 downto 0) := TODO_VISIBLE_DIVIDER;
    signal div_count : unsigned(26 downto 0) := (others => '0');
    signal slow_clk  : std_logic := '0';
    signal count     : unsigned(3 downto 0) := (others => '0');

    function seg(d : unsigned(3 downto 0)) return std_logic_vector is
    begin
        case d is
            when "0000" => return "1000000"; when "0001" => return "1111001";
            when "0010" => return "0100100"; when "0011" => return "0110000";
            when "0100" => return "0011001"; when "0101" => return "0010010";
            when "0110" => return "0000010"; when "0111" => return "1111000";
            when "1000" => return "0000000"; when "1001" => return "0010000";
            when "1010" => return "0001000"; when "1011" => return "0000011";
            when "1100" => return "1000110"; when "1101" => return "0100001";
            when "1110" => return "0000110"; when "1111" => return "0001110";
            when others => return "1111111";
        end case;
    end function;
begin
    process(CLOCK_50)
    begin
        if rising_edge(CLOCK_50) then
            if KEY(0) = '0' then
                div_count <= (others => '0');
                slow_clk <= '0';
            elsif div_count = TODO_DIVIDER_LIMIT then
                div_count <= (others => '0');
                slow_clk <= not slow_clk;
            else
                div_count <= div_count + 1;
            end if;
        end if;
    end process;

    process(slow_clk, KEY)
    begin
        if KEY(0) = '0' then count <= (others => '0');
        elsif rising_edge(slow_clk) then count <= TODO_NEXT_COUNT;
        end if;
    end process;

    LEDR(3 downto 0) <= TODO_LED_COUNT;
    LEDR(9 downto 4) <= (others => '0');
    HEX0 <= TODO_HEX_COUNT;
end architecture rtl;

Suponha que o registrador do divisor tivesse 24 bits. Qual é o maior valor possível (2^24 − 1), e DIV = 33.333.333 ainda caberia? Explique em uma frase.

3

Execute e reinicie o contador

10 min

Monte o contador na placa real, observe-o avançar, reinicie-o e depois experimente os outros dois valores de divisor.

  1. Abra o laboratório VHDL da DE1-SoC; o editor mostra blink.vhd.

  2. Substitua todo o arquivo pelo contador acima, complete cada TODO, mantenha a entidade blink e confirme CLOCK_50 : in std_logic.

  3. Clique em Synthesize. Feche ou pule o tour se ele cobrir o botão. Aguarde de 1 a 3 minutos, corrija erros e repita até obter sucesso com 0 erros. Avisos de temporização são esperados neste divisor didático.

  4. Clique em Upload to FPGA e aguarde a placa e a câmera.

  5. Observe LEDR(3 downto 0) e HEX0. Um valor novo deve surgir aproximadamente a cada 1,33 s. Confirme que os LEDs binários combinam com o dígito hexadecimal; por exemplo, C corresponde a 1100.

  6. Pressione e mantenha KEY(0) para reiniciar em 0; solte-o e observe a contagem recomeçar.

  7. Opcional: troque DIV por 8.333.333 (to_unsigned(8_333_333, 27)), sintetize e observe a contagem mais rápida. Depois experimente 100.000.000 (to_unsigned(100_000_000, 27)) e observe-a mais lenta. Esta extensão não é necessária para a evidência obrigatória.

Com DIV = 33.333.333, registre alguns valores à medida que aparecem. Para cada um, anote o padrão binário em LEDR(3 downto 0), o caractere em HEX0 e o valor decimal. Depois reinicie com KEY(0) e confirme o retorno a 0.

Observação nº LEDR(3 downto 0) (binário) Caractere em HEX0 Valor decimal (0–15)

Envie sua evidência. Quando HEX0 mostrar C (decimal 12), faça uma captura da câmera; LEDR(3 downto 0) deve mostrar 1100 ao mesmo tempo.

Se você executou a extensão com DIV = 8.333.333 e DIV = 100.000.000, qual valor produziu a contagem mais rápida e, aproximadamente, quantas vezes mais rápida ela foi?

Para usar um contador como relógio com erro máximo de um segundo em 24 horas, o que precisaria estar correto além do contador? Cite o ingrediente principal e explique por que ele importa.

4

Extensão opcional: contador BCD de 0 a 9

7 min

Um contador comum de 4 bits percorre 015 e mostra AF nos seis valores finais. Muitos displays precisam apenas de um dígito decimal: 0, 1, 2, …, 9 e depois 0. Isso é um contador BCD (decimal codificado em binário).

Em vez de deixar o registrador voltar sozinho depois de 15, detecte 9 e force o próximo valor para 0. Substitua o processo do contador por:

process(slow_clk, KEY)
begin
    if KEY(0) = '0' then count <= TODO_RESET_COUNT;
    elsif rising_edge(slow_clk) then
        if count = TODO_LAST_BCD_DIGIT then count <= TODO_WRAP_VALUE;
        else                              count <= TODO_NEXT_COUNT;
        end if;
    end if;
end process;

Mantenha o divisor, as atribuições concorrentes de LEDR/HEX0 e a função seg. Agora HEX0 deve percorrer apenas 0 a 9.

  1. Edite a entidade para que a contagem volte a 0 depois de 9.

  2. Sintetize, corrija qualquer erro e envie para a FPGA.

  3. Observe HEX0 percorrer 0, 1, 2, …, 9, 0, 1, … sem chegar a A. Use KEY(0) para reiniciar se necessário.

(Extensão BCD opcional) Qual é o valor de count na borda seguinte à exibição de 9, e qual linha força esse valor? Se você executou a extensão, acrescente a observação em que HEX0 volta a 0.

5

Extensão opcional: registrador de deslocamento de 8 bits

9 min

Um registrador de deslocamento é uma fileira de flip-flops em que, a cada borda, cada bit passa ao vizinho e um bit novo entra por uma extremidade. Ele é fundamental em enlaces seriais como UART, SPI e I2C, que enviam dados um bit de cada vez.

O registrador shreg(7 downto 0) executa, em cada borda de subida de slow_clk com o deslocamento habilitado:

shreg <= TODO_SHIFT_LEFT_UPDATE;  -- TODO: deslocar à esquerda e inserir SW(0) no bit 0

O lado direito usa os sete bits inferiores shreg(6 downto 0), move-os uma posição para cima e insere SW(0) no bit 0. Os bits caminham para a extremidade alta, portanto é um deslocamento à esquerda. SW(0) é a entrada serial, KEY(0) é reset e KEY(1) habilita o deslocamento; ambos os botões são ativos em nível baixo. Mantenha KEY(1) pressionado para deslocar a cada pulso.

Padrão esperado. Partindo de 0000_0000, aplique quatro 1: 0000_0001, 0000_0011, 0000_0111, 0000_1111. Depois aplique quatro 0: 0001_1110, 0011_1100, 0111_1000 e 1111_0000.

Valor versus aparência. Registre tanto o valor binário quanto a câmera. A ordem física dos LEDs pode parecer espelhada em relação à escrita (shreg(7) de um lado e shreg(0) do outro). Isso é efeito de orientação, não erro lógico.

-- Código inicial do estudante: preencha as linhas TODO antes de sintetizar.
-- Lição 4 - Exercício 2-D: registrador de 8 bits deslocando à esquerda.
-- SW(0)=entrada serial, KEY(0)=reset, KEY(1)=habilita deslocamento. KEY é ativo em nível baixo.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity blink is
    port (
        CLOCK_50 : in  std_logic;
        KEY      : in  std_logic_vector(3 downto 0);
        SW       : in  std_logic_vector(9 downto 0);
        LEDR     : out std_logic_vector(9 downto 0)
    );
end entity blink;

architecture rtl of blink is
    constant DIV : unsigned(26 downto 0) := TODO_VISIBLE_DIVIDER;
    signal div_count : unsigned(26 downto 0) := (others => '0');
    signal slow_clk  : std_logic := '0';
    signal shreg     : std_logic_vector(7 downto 0) := (others => '0');
begin
    process(CLOCK_50)
    begin
        if rising_edge(CLOCK_50) then
            if KEY(0) = '0' then
                div_count <= (others => '0'); slow_clk <= '0';
            elsif div_count = DIV then
                div_count <= (others => '0'); slow_clk <= not slow_clk;
            else
                div_count <= div_count + 1;
            end if;
        end if;
    end process;

    process(slow_clk, KEY)
    begin
        if KEY(0) = '0' then
            shreg <= (others => '0');
        elsif rising_edge(slow_clk) then
            if KEY(1) = '0' then shreg <= TODO_SHIFT_UPDATE;
            end if;
        end if;
    end process;

    LEDR(7 downto 0) <= TODO_SHIFT_OUTPUT;
    LEDR(9 downto 8) <= (others => '0');
end architecture rtl;

Partindo de 0000_0000, com KEY(1) pressionado, aplique SW(0) = 1, 1, 0, 1 em quatro pulsos, do mais antigo ao mais recente. Calcule passo a passo e informe shreg(7 downto 0) ao final.

6

Extensão opcional: faça o bit percorrer os LEDs

9 min

Monte o registrador, observe os bits acesos percorrerem a fileira e depois veja os zeros segui-los.

  1. Substitua blink.vhd pelo registrador de deslocamento acima, complete os TODOs, mantenha blink e CLOCK_50 : in std_logic.

  2. Sintetize, corrija erros e envie para a FPGA.

  3. Pressione e solte KEY(0) para zerar o registrador.

  4. Coloque SW(0)=1 e mantenha KEY(1) pressionado. Após quatro pulsos, quatro LEDs estarão acesos (0000_1111).

  5. Coloque SW(0)=0, mantenha KEY(1) pressionado e aguarde quatro pulsos: o valor chega a 1111_0000.

  6. Solte KEY(1) para congelar o padrão e facilitar a leitura e a captura.

Reinicie e mantenha KEY(1) pressionado. Registre quatro pulsos com SW(0)=1 e depois quatro com SW(0)=0. Escreva shreg como oito bits, com shreg(7) à esquerda, e indique se os LEDs parecem espelhados na câmera.

Pulso nº Entrada SW(0) shreg(7 downto 0) (8 bits) Câmera: LEDs acesos / espelhados?

Envie sua evidência. Depois de quatro 1 e quatro 0, solte KEY(1) para congelar shreg = 1111_0000, faça uma captura e anote se os LEDs parecem espelhados.

Compare o valor calculado (1111_0000) com os LEDs da captura. Se parecerem espelhados, explique por que isso é efeito da orientação/rotulagem e não um erro no deslocamento.

7

Opcional: deslocamento à direita e contador em anel

6 min

Desloque para o outro lado. Para mover cada bit para a extremidade baixa e inserir o novo bit no topo (shreg(7)), troque apenas a atribuição:

shreg <= TODO_SHIFT_RIGHT_UPDATE; -- TODO: deslocar à direita e inserir SW(0) no bit 7

Agora SW(0) entra em shreg(7) e shreg(7 downto 1) desce para shreg(6 downto 0).

Extensão: contador em anel. Um contador em anel realimenta a saída do próprio registrador, fazendo um padrão circular para sempre. Crie um anel com deslocamento à esquerda, realimentando shreg(7) no bit 0. O reset deve carregar "00000001", e a linha deve ser shreg <= shreg(6 downto 0) & shreg(7);. Mantenha KEY(1) pressionado para observar um LED circulando.

  1. Troque a única linha de deslocamento pela versão à direita, sintetize e envie.

  2. Reinicie, mantenha KEY(1) pressionado, aplique quatro 1 e quatro 0 e confirme o movimento no sentido oposto.

  3. Opcional: faça o contador em anel com reset em "00000001" e realimentação de shreg(7) no bit 0.

Na versão à direita shreg <= SW(0) & shreg(7 downto 1);, parta de 0000_0000 e aplique SW(0)=1 por quatro pulsos. Qual extremidade se preenche e qual é o valor final?

8

O que você construiu

4 min

Em três frases, explique: (1) como o divisor tornou visível o movimento do contador, (2) uma evidência concreta capturada no contador e em HEX0 e (3) um risco evitado, como uma constante que não cabe no registrador de 27 bits. Se você executou uma extensão BCD ou de deslocamento, acrescente uma frase com essa evidência.