Teach Lições com laboratórios remotos

Lição do Teach

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)
  • Sistemas digitais e FPGA

Entre com uma conta de professor para preparar uma sessão. Os estudantes entram com um código da turma.

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.
Altera DE1-SoC

Objetivos de aprendizagem

  • 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.

Visualização da atividade do estudante

Conteúdo da atividade

Apenas visualização. Em uma sessão de aula, os estudantes podem preencher respostas e entregar o trabalho ao professor.

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 50 milhões de passos por segundo. Sua sequência completa de 16 estados se repete 3,125 milhões de vezes por segundo. A câmera remota captura apenas alguns quadros 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.

Transforme esta visualização em uma sessão de aula

Continue para o LabsLand Teach para usar esta lição com seus estudantes e conferir o acesso disponível para sua conta.