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.
Entre com uma conta de professor para preparar uma sessão. Os estudantes entram com um código da turma.
Ainda não tem uma conta na LabsLand? Criar sua conta de professor
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.
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 HEX0–HEX5 (sete segmentos ativos em nível baixo).
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.
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: 0–9 e depois A–F para 10–15.
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.
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.
Abra o laboratório VHDL da DE1-SoC; o editor mostra
blink.vhd.Substitua todo o arquivo pelo contador acima, complete cada TODO, mantenha a entidade
blinke confirmeCLOCK_50 : in std_logic.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.
Clique em Upload to FPGA e aguarde a placa e a câmera.
Observe
LEDR(3 downto 0)eHEX0. 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,Ccorresponde a1100.Pressione e mantenha
KEY(0)para reiniciar em0; solte-o e observe a contagem recomeçar.Opcional: troque
DIVpor8.333.333(to_unsigned(8_333_333, 27)), sintetize e observe a contagem mais rápida. Depois experimente100.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.
Extensão opcional: contador BCD de 0 a 9
7 min
Um contador comum de 4 bits percorre 0–15 e mostra A–F 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.
Edite a entidade para que a contagem volte a
0depois de9.Sintetize, corrija qualquer erro e envie para a FPGA.
Observe
HEX0percorrer0, 1, 2, …, 9, 0, 1, …sem chegar aA. UseKEY(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.
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.
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.
Substitua
blink.vhdpelo registrador de deslocamento acima, complete os TODOs, mantenhablinkeCLOCK_50 : in std_logic.Sintetize, corrija erros e envie para a FPGA.
Pressione e solte
KEY(0)para zerar o registrador.Coloque
SW(0)=1e mantenhaKEY(1)pressionado. Após quatro pulsos, quatro LEDs estarão acesos (0000_1111).Coloque
SW(0)=0, mantenhaKEY(1)pressionado e aguarde quatro pulsos: o valor chega a1111_0000.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.
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.
Troque a única linha de deslocamento pela versão à direita, sintetize e envie.
Reinicie, mantenha
KEY(1)pressionado, aplique quatro1e quatro0e confirme o movimento no sentido oposto.Opcional: faça o contador em anel com reset em
"00000001"e realimentação deshreg(7)no bit0.
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?
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.
Ainda não tem uma conta na LabsLand? Criar sua conta de professor