Teach lesson
Lógica digital na DE1-SoC (4/6): contadores e registradores de deslocamento
Implemente contadores e registradores de deslocamento em Verilog, observe padrões sequenciais nos LEDs e explique atualizações acionadas por clock no hardware.
New to LabsLand? Create your teacher account
Learning Outcomes
Explicar o que faz um divisor de clock e usá-lo para tornar o comportamento a 50 MHz visível aos olhos e à câmera.
Construir um contador de 4 bits e ler seu valor nos LEDs e em um display de sete segmentos.
Construir um registrador de deslocamento e raciocinar sobre a ordem dos bits em relação à ordem física dos LEDs na câmera.
Reconhecer problemas de largura de registrador, 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.
Do armazenamento ao movimento: por que precisamos de um divisor de clock
10 min
Na Lição 3, você construiu circuitos que se lembram de um bit ou de um byte: um flip-flop e um registrador de carga paralela. Cada um atualiza o valor armazenado em uma borda do clock. Nesta lição, você reutilizará essa ideia para construir circuitos que se movem sozinhos a cada borda: um contador que avança por 0, 1, 2, 3, … e um registrador de deslocamento que move seus bits para o lado.
O clock da placa é CLOCK_50, uma onda quadrada de 50 MHz, com 50.000.000 de bordas de subida por segundo. Se um contador avançasse a cada borda de CLOCK_50, ele percorreria todos os dezesseis valores de um contador de 4 bits cerca de três milhões de vezes por segundo. Você veria apenas um borrão estável de LEDs, e a câmera remota, que captura somente alguns quadros por segundo, não teria nenhuma chance de acompanhar.
Por isso, reduzimos a velocidade com um divisor de clock. O divisor também é apenas um contador: ele conta as bordas de CLOCK_50 e, ao atingir uma contagem terminal escolhida, DIV, alterna um sinal mais lento chamado slow_clk e volta a contar. Quanto maior for DIV, mais lento será slow_clk. Em seguida, acionamos o circuito *real* — o contador de 4 bits ou o registrador de deslocamento — com slow_clk em vez de CLOCK_50.
Como em todas as lições, os projetos sequenciais precisam declarar o clock. O modelo padrão leds_mirror.v lista apenas chaves, botões e LEDs e não inclui clock; portanto, cada módulo desta lição adiciona input CLOCK_50; à lista de portas. Os sinais da placa continuam os mesmos: SW[9:0], com uma chave para cima representando 1; KEY[3:0], ativa em nível baixo, de modo que um botão pressionado apresenta 0; LEDR[9:0], ativa em nível alto, acendendo com 1; e HEX0–HEX5, displays de sete segmentos ativos em nível baixo, cujos segmentos acendem com 0.
Um divisor de clock conta as bordas rápidas de CLOCK_50 e só alterna o sinal muito mais lento slow_clk quando alcança a contagem terminal DIV. Aumentar DIV torna slow_clk mais lento. O circuito real é acionado por slow_clk, por isso seu movimento se torna visível.
Um contador de 4 bits acionado diretamente por CLOCK_50 avança cerca de três milhões de vezes por segundo, enquanto a câmera remota captura somente alguns quadros por segundo. O que você realmente veria nos LEDs, e por quê?
Se você dobrasse a contagem terminal DIV, por exemplo de 33.333.333 para 66.666.666, aproximadamente como mudaria a *frequência* dos passos 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 é simplesmente o valor atual mais um. Um contador de 4 bits armazena valores de 0 a 15; depois de 15, ele volta para 0 automaticamente, pois 15 + 1 = 16 = 10000 em binário e somente os quatro bits inferiores são preservados, 0000. Não é necessário código especial para esse retorno: a largura do registrador faz isso.
O projeto abaixo tem duas partes. O divisor conta bordas de CLOCK_50 até DIV e alterna slow_clk. O contador avança uma unidade a cada borda de subida de slow_clk, e KEY[0] o reinicia em 0; como o botão é ativo em nível baixo, mantê-lo pressionado ativa o reset. A contagem aciona LEDR[3:0] em binário e também HEX0 como um único dígito hexadecimal: 0–9 e depois A–F para 10–15.
A temporização, com precisão. Com DIV = 33.333.333, o divisor alterna slow_clk depois de aproximadamente 0,67 segundo, pois 33.333.333 bordas em 50.000.000 por segundo equivalem a cerca de dois terços de segundo. Um passo do contador ocorre apenas em uma borda de subida de slow_clk, e uma onda quadrada tem uma borda de subida por *período completo*: uma transição de baixo para alto e outra de alto para baixo. Assim, o contador avança aproximadamente a cada 1,33 segundo, ou cerca de 0,75 Hz. Espere um novo valor um pouco menos de uma vez por segundo.
Um problema de largura de registrador. O registrador do divisor tem 27 bits de propósito. Um registrador de 26 bits só pode armazenar até 2^26 - 1 = 67.108.863. Um valor como DIV = 100.000.000 simplesmente não cabe em 26 bits, portanto um projeto com essa largura já estaria incorreto antes de executar. Com 27 bits, o registrador armazena até 2^27 - 1 = 134.217.727; assim, tanto DIV = 100.000.000, mais lento, quanto DIV = 8.333.333, mais rápido, cabem e funcionam. Sempre confirme que uma constante cabe no registrador declarado.
Uma ressalva importante. Acionar um registrador diretamente com um slow_clk dividido, como fazemos aqui, é um atalho didático comum porque torna a ideia concreta. Em projetos de produção, normalmente se mantém tudo no único clock rápido CLOCK_50 e se usa uma habilitação de clock: um pulso de um ciclo que diz ao registrador “atualize nesta borda”. Assim, todo o projeto permanece em um clock real, que as ferramentas de síntese conseguem analisar muito melhor. Usamos o clock dividido aqui apenas por ser a forma mais clara de *ver* o contador se mover.
// Licao 4 - Exercicio 2-C: modelo inicial de contador de 4 bits com divisor de clock
// KEY[0]=reset. LEDR[3:0]=contagem, HEX0=contagem como digito hexadecimal.
module leds_mirror(CLOCK_50, KEY, LEDR, HEX0);
input CLOCK_50;
input [3:0] KEY;
output [9:0] LEDR;
output [6:0] HEX0;
localparam [26:0] DIV = TODO_DIVIDER_LIMIT;
reg [26:0] div;
reg slow_clk;
always @(posedge CLOCK_50) begin
if (!KEY[0]) begin div <= 0; slow_clk <= 0; end
else if (div == DIV) begin div <= 0; slow_clk <= ~slow_clk; end
else div <= div + 1'b1;
end
reg [3:0] count;
always @(posedge slow_clk or negedge KEY[0]) begin
if (!KEY[0]) count <= TODO_RESET_COUNT;
else count <= TODO_NEXT_COUNT;
end
assign LEDR[3:0] = TODO_LED_COUNT;
assign LEDR[9:4] = 6'b0;
assign HEX0 = TODO_HEX_PATTERN_FOR_COUNT;
endmoduleSuponha que você tenha usado um registrador de divisor de 24 bits. Qual é o maior valor que ele pode armazenar? Mostre 2^24 − 1. DIV = 33.333.333 ainda caberia? Explique em uma frase.
Execute o contador, reinicie-o e mude sua velocidade
12 min
Construa o contador na placa real, observe-o avançar, reinicie-o e depois teste os outros dois valores de divisor.
Abra o laboratório Verilog da DE1-SoC. O editor mostra o arquivo
leds_mirror.v.Selecione todo o conteúdo de
leds_mirror.ve substitua-o pelo módulo do contador de 4 bits acima. Mantenha o nomeleds_mirror. Observe que ele declarainput CLOCK_50;: o modelo não adiciona o clock por você.Clique em Sintetizar (
Synthesize). Se um tour guiado cobrir o botão, feche ou pule o tour primeiro. Espere cerca de 1 a 3 minutos. Corrija qualquer erro de digitação e sintetize novamente. O Quartus pode relatar muitos avisos de temporização por causa deste divisor didático; prossiga somente quando o estado final indicar sucesso com 0 erros.Clique em Enviar para a FPGA (
Upload to FPGA) e espere a sessão da placa e a câmera.Observe
LEDR[3:0]eHEX0. Um novo valor deve aparecer a cada pouco mais de um segundo, aproximadamente a cada 1,33 s. Confirme que os LEDs binários correspondem ao dígito hexadecimal emHEX0; por exemplo, quandoHEX0mostrarC,LEDR[3:0]deve mostrar1100.Mantenha
KEY[0]pressionado para reiniciar a contagem em0; solte-o e observe a contagem recomeçar em0.Mude
DIVpara8.333.333, escrevendo27'd8_333_333, sintetize, envie e observe que o contador fica nitidamente mais rápido. Depois mudeDIVpara100.000.000, escrevendo27'd100_000_000, sintetize, envie e observe que ele fica mais lento. Os dois valores cabem porque o registrador tem 27 bits.
Com o DIV padrão de 33.333.333, observe o contador e registre alguns valores à medida que aparecem. Em cada linha, escreva o padrão binário LEDR[3:0] e o caractere mostrado em HEX0 (0–9 e depois A–F). Em seguida, reinicie com KEY[0] e confirme que a contagem volta a 0.
| Observação nº | LEDR[3:0] (binário) | Caractere em HEX0 | Valor decimal (0–15) |
|---|---|---|---|
Envie sua evidência. Espere um momento em que HEX0 mostre o dígito C, decimal 12, faça uma captura da câmera — LEDR[3:0] deve mostrar 1100 ao mesmo tempo — e anexe-a abaixo.
Você executou o laboratório com DIV = 8.333.333 e com DIV = 100.000.000. Qual valor fez o contador avançar mais rápido e, usando os dois números, aproximadamente quantas vezes mais rápido ele ficou em relação ao outro?
Suponha que você queira usar um contador como este como relógio, mantendo a precisão dentro de mais ou menos um segundo ao longo de 24 horas. Além do próprio contador, o que precisaria estar correto? Indique o componente principal e explique por que ele importa.
Modificação: contador BCD de 0 a 9
7 min
Um contador simples de 4 bits percorre 0–15 e mostra A–F nos seis valores mais altos. Muitos displays reais, porém, precisam de um único dígito decimal: contar 0, 1, 2, …, 9 e voltar diretamente para 0. Um contador que percorre apenas 0–9 é chamado contador BCD, de decimal codificado em binário.
A mudança é pequena: em vez de deixar o registrador de 4 bits retornar sozinho depois de 15, você detecta quando a contagem chega a 9 e força o próximo valor a 0. Substitua o bloco always do contador por uma lógica com este formato:
reg [3:0] count;
always @(posedge slow_clk or negedge KEY[0]) begin
if (!KEY[0]) count <= TODO_RESET_COUNT;
else if (count == TODO_LAST_BCD_DIGIT) count <= TODO_WRAP_VALUE;
else count <= TODO_NEXT_COUNT;
end
Mantenha todo o restante — o divisor, as atribuições de LEDR/HEX0 e a função seg — igual. Agora HEX0 deve percorrer 0 a 9 e nunca mostrar A–F.
Edite o módulo do contador para que o bloco
alwaysvolte a0depois de9, conforme mostrado acima.Sintetize, corrija qualquer erro e selecione Enviar para a FPGA (
Upload to FPGA).Observe
HEX0percorrer0, 1, 2, … 9, 0, 1, …. Confirme que ele nunca chega aA. PressioneKEY[0]para reiniciar em0se quiser verificar novamente desde o início.
No contador BCD, qual é o valor de count na borda do clock imediatamente posterior ao momento em que HEX0 mostra 9, e qual linha de código força esse valor?
Exercício 2-D: registrador de deslocamento de 8 bits
9 min
Um registrador de deslocamento é uma fileira de flip-flops na qual, a cada borda do clock, cada bit se move para seu vizinho e um bit novo entra por uma das extremidades. Ele está no centro das ligações seriais: protocolos como UART, SPI e I2C enviam dados um bit de cada vez, e o registrador de deslocamento reúne esses bits em um byte ou separa um byte bit por bit.
O projeto abaixo usa um registrador de 8 bits, shreg[7:0]. Em cada borda de subida de slow_clk, enquanto o deslocamento estiver habilitado, ele executa:
shreg <= TODO_SHIFT_LEFT_UPDATE; // desloque para a esquerda e insira SW[0] no bit correto
Leia o lado direito com atenção: ele pega os sete bits inferiores shreg[6:0], move-os uma posição para cima e insere SW[0] no bit 0. Cada bit se move em direção à extremidade alta, portanto este é um deslocamento para a esquerda. Os controles são SW[0] = bit serial de entrada, KEY[0] = reset ativo em nível baixo e KEY[1] = habilitação de deslocamento ativa em nível baixo: mantenha KEY[1] pressionada para deslocar a cada pulso.
O padrão esperado. Raciocine em vez de confiar na orientação da câmera. Comece em 0000_0000. Ajuste SW[0] = 1 e deixe passar quatro pulsos: o registrador é preenchido a partir do bit 0 e produz 0000_0001, 0000_0011, 0000_0111, 0000_1111. Agora ajuste SW[0] = 0 e deixe passar mais quatro pulsos: zeros entram no bit 0 e empurram os uns para cima e para fora, produzindo 0001_1110, 0011_1100, 0111_1000 e, por fim, 1111_0000. Portanto, depois de quatro 1s seguidos de quatro 0s, shreg[7:0] vale 1111_0000.
Valor dos bits versus o que você vê. Registre tanto o valor binário do registrador quanto o que a câmera mostra. A ordem física dos LEDs na placa pode parecer espelhada em relação à escrita dos bits, com shreg[7] em uma extremidade e shreg[0] na outra. Assim, os LEDs acesos podem parecer invertidos da esquerda para a direita em comparação com 1111_0000. Isso é um efeito de rotulagem e orientação, não um erro lógico; é exatamente por isso que você deve anotar o valor calculado junto à foto.
// Licao 4 - Exercicio 2-D: modelo inicial de registrador de deslocamento de 8 bits
// SW[0]=dado serial de entrada, KEY[0]=reset, KEY[1]=habilitacao de deslocamento.
module leds_mirror(CLOCK_50, SW, KEY, LEDR);
input CLOCK_50;
input [9:0] SW;
input [3:0] KEY;
output [9:0] LEDR;
localparam [24:0] DIV = TODO_DIVIDER_LIMIT;
reg [24:0] div;
reg slow_clk;
always @(posedge CLOCK_50) begin
if (!KEY[0]) begin div <= 0; slow_clk <= 0; end
else if (div == DIV) begin div <= 0; slow_clk <= ~slow_clk; end
else div <= div + 1'b1;
end
reg [7:0] shreg;
always @(posedge slow_clk or negedge KEY[0]) begin
if (!KEY[0]) shreg <= TODO_RESET_VECTOR;
else if (!KEY[1]) shreg <= TODO_SHIFT_LEFT_UPDATE;
end
assign LEDR[7:0] = TODO_SHIFT_REGISTER_OUTPUT;
assign LEDR[9:8] = 2'b00;
endmoduleComeçando em 0000_0000, com KEY[1] mantida pressionada, aplique a sequência de entrada SW[0] = 1, 1, 0, 1 ao longo de quatro pulsos, do bit mais antigo para o mais recente. Calcule cada pulso e escreva shreg[7:0] depois dos quatro.
Faça o bit percorrer os LEDs
9 min
Construa o registrador de deslocamento, observe um único LED aceso percorrer a fileira e depois veja os zeros segui-lo.
Substitua o conteúdo de
leds_mirror.vpelo módulo do registrador de deslocamento de 8 bits acima. Mantenha o nomeleds_mirror. Ele declarainput CLOCK_50;.Sintetize, corrija qualquer erro e selecione Enviar para a FPGA (
Upload to FPGA).Pressione e solte
KEY[0]uma vez para reiniciar o registrador em zero; nenhum dos LEDsLEDR[7:0]deve ficar aceso.Coloque
SW[0]para cima, entrada serial =1, e mantenhaKEY[1]pressionada. A cada pulso, aproximadamente a cada 1,33 s, entra um novo LED aceso em uma extremidade e os já acesos avançam. Deixe passar quatro pulsos: você deve ter quatro LEDs acesos,0000_1111.Agora coloque
SW[0]para baixo, entrada serial =0, continue mantendoKEY[1]pressionada e deixe passar mais quatro pulsos. Os zeros seguem os uns até o registrador mostrar1111_0000.Solte
KEY[1]a qualquer momento para congelar o padrão atual e facilitar sua leitura e fotografia.
Reinicie e mantenha KEY[1] pressionada. Registre o valor do registrador pulso a pulso: quatro pulsos com SW0=1 e depois quatro com SW0=0. Escreva shreg com oito bits (shreg[7] à esquerda, shreg[0] à direita). Na última coluna, indique se os LEDs acesos na câmera parecem espelhados em relação aos bits escritos.
| Pulso nº | Entrada SW[0] | shreg[7:0] (8 bits) | Câmera: quais LEDs acesos / espelhados? |
|---|---|---|---|
Envie sua evidência. Depois de quatro pulsos com SW0=1 e quatro com SW0=0, solte KEY[1] para congelar o padrão, shreg = 1111_0000, faça uma captura da câmera, anote se os LEDs acesos parecem espelhados e anexe-a abaixo.
Compare o valor binário calculado, 1111_0000, com os LEDs acesos na captura. Se parecerem espelhados, explique por que isso é um efeito de rotulagem e orientação, e não um erro na lógica de deslocamento.
Modificação: deslocamento à direita e contador em anel
6 min
Desloque para o outro lado. Para fazer o registrador se deslocar para a direita em vez de para a esquerda — cada bit em direção à extremidade baixa, com o novo bit entrando pela extremidade alta, shreg[7] —, você muda uma única linha. Substitua o espaço reservado do deslocamento à esquerda por sua própria expressão de deslocamento à direita.
else if (!KEY[1]) shreg <= TODO_SHIFT_RIGHT_UPDATE; // desloque para a direita e insira SW[0]
Somente essa linha muda. Agora SW[0] entra em shreg[7], e os sete bits superiores shreg[7:1] descem para shreg[6:0].
Extensão — contador em anel. Um contador em anel realimenta a própria saída do registrador em sua entrada, em vez de receber um novo bit externo, de modo que um padrão fixo circula indefinidamente. Construa um anel que se desloca para a esquerda e realimenta o bit superior no bit inferior. Inicialize o registrador em 0000_0001 para que exatamente um LED aceso percorra a fileira. Use um reset que carregue 8'h01, não 8'h00. Mantenha KEY[1] pressionada e observe um LED circular sem parar.
Mude a única linha de deslocamento do módulo para a versão à direita descrita acima. Sintetize e envie.
Reinicie, mantenha
KEY[1]pressionada, apliqueSW[0]=1por quatro pulsos e depoisSW[0]=0por quatro pulsos e confirme que os bits agora se movem na direção oposta pelos LEDs.Extensão opcional: construa o contador em anel. O reset carrega
8'h01, e a linha de deslocamento realimentashreg[7]no bit0. MantenhaKEY[1]pressionada e observe um LED aceso circular.
Em sua versão de deslocamento para a direita, comece em 0000_0000 e insira SW[0] = 1 por quatro pulsos. Qual extremidade do registrador é preenchida e qual é o valor de shreg[7:0] depois dos quatro?
Em três frases, explique: (1) o que o divisor de clock fez para permitir ver o contador e o registrador de deslocamento se moverem; (2) uma evidência concreta que você capturou, como o valor do contador em HEX0 ou o padrão 1111_0000; e (3) um problema que evitou, como uma constante que não cabe no registrador de 26 bits ou o valor dos bits em relação à ordem espelhada dos LEDs.