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.
New to LabsLand? Create your teacher account
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.
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 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.
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.