Lição do Teach
Lógica digital na DE1-SoC (3/6): memória — flip-flops e registradores
Construa flip-flops D e registradores em Verilog, teste o comportamento de clock e reset e diferencie lógica combinacional de estado armazenado.
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 por que a lógica sequencial precisa de memória e a lógica combinacional não.
Descrever um flip-flop D como amostragem e retenção na borda do clock, com reset síncrono.
Construir um registrador de carga paralela de 8 bits com flip-flops D que compartilham clock e reset.
Usar atribuição não bloqueante em blocos acionados por clock e reconhecer entradas KEY ativas em nível baixo.
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.
Por que os circuitos precisam de memória
10 min
Todos os projetos até agora foram combinacionais: os LEDs eram uma função pura das posições das chaves naquele instante. Ao mudar uma chave, a saída muda assim que o sinal se propaga pelas portas. Um circuito combinacional não tem estado: ele não sabe quais eram as entradas um instante antes e não consegue se lembrar de nada.
Mas a maioria dos sistemas úteis precisa de memória. Um contador deve conhecer seu valor atual para produzir o próximo. Uma CPU precisa manter operandos e resultados entre instruções. Um semáforo deve saber qual cor está acesa para decidir o que vem depois. Nada disso é possível apenas com portas, porque elas sempre refletem somente suas entradas atuais.
Lógica sequencial é a lógica que se lembra. Sua saída depende não só das entradas atuais, mas também de um estado armazenado: o que aconteceu antes. O bloco básico que armazena um bit de estado é o flip-flop, e o marcador de tempo que informa a todos os flip-flops quando atualizar é o clock.
A DE1-SoC fornece um clock livre de 50 MHz chamado CLOCK_50: uma onda quadrada que sobe e desce 50.000.000 de vezes por segundo. A partir desta lição, seus projetos sequenciais devem listá-lo como entrada. O modelo padrão leds_mirror.v lista apenas chaves, botões e LEDs e não inclui clock; portanto, você mesmo adicionará input CLOCK_50; ao módulo. Esquecer essa entrada é o erro mais comum em projetos sequenciais.
Qual destes circuitos é sequencial — sua saída pode depender do passado — e não combinacional?
Os projetos combinacionais das Lições 1–2 listavam apenas chaves e LEDs, mas um projeto acionado por clock, como um flip-flop, também deve listar input CLOCK_50;. Por que um flip-flop ou registrador precisa do clock como entrada, e o que costuma acontecer se você omiti-lo? Responda em uma ou duas frases.
O flip-flop D: amostragem e retenção na borda do clock
11 min
Um flip-flop D é o elemento de memória mais simples. Ele tem uma entrada de dados D, uma entrada de clock e uma saída Q. Sua regra completa é:
A cada borda de subida do clock,Qcaptura o valor deDe o mantém até a próxima borda de subida.
Entre as bordas, nada que aconteça na entrada importa: Q fica congelado. Isso é amostragem e retenção: o flip-flop amostra D no instante em que o clock passa de 0 para 1 e mantém essa amostra estável. É assim que um circuito ganha memória: Q pode continuar em 1 muito depois de D voltar a 0, pois se lembra da amostra.
Amostragem e retenção na borda do clock: Q só é atualizado em cada borda de subida de CLOCK_50, copiando o valor de D naquele instante e mantendo-o até a próxima borda. Mudanças em D entre as bordas são ignoradas.
Como escrever isso em Verilog — duas ideias novas.
Primeiro, o bloco acionado por clock. Descrevemos um comportamento acionado por borda com always @(posedge CLOCK_50): as instruções internas são executadas uma vez a cada borda de subida, e não continuamente. Um sinal atribuído dentro desse bloco deve ser declarado como reg, pois mantém um valor entre as bordas.
Segundo, a atribuição não bloqueante <=. Dentro de um bloco acionado por clock, use sempre <=, não =. O operador <= significa “amostre agora todos os lados direitos e depois atualize os lados esquerdos juntos na borda”, exatamente como se comportam flip-flops reais: todos amostram ao mesmo tempo. Usar = — uma atribuição bloqueante — dentro de um bloco acionado por clock é um erro clássico em Verilog: as atribuições passam a valer em sequência dentro da mesma borda, e um flip-flop pode enxergar o valor novo de outro em vez do valor anterior, produzindo um hardware diferente do pretendido. Regra prática: <= em blocos acionados por clock; = apenas em lógica combinacional com assign ou always @(*).
Reset síncrono. Também queremos uma forma de forçar Q a voltar a um valor conhecido. Um reset síncrono é verificado dentro do bloco acionado por clock, portanto só entra em vigor em uma borda do clock:
always @(posedge CLOCK_50) begin
if (!KEY[0]) Q <= TODO_RESET_VALUE; // reset sincrono, avaliado na borda
else Q <= TODO_CAPTURED_VALUE;
end
Como KEY é ativa em nível baixo, o botão apresenta 0 quando pressionado; portanto, !KEY[0] é verdadeiro exatamente enquanto o botão de reset está pressionado.
Suponha que você tenha escrito Q = SW[0]; com um = bloqueante, em vez de <=, dentro de always @(posedge CLOCK_50). Qual operador deveria ter usado, e o que pode dar errado quando vários flip-flops em um mesmo bloco acionado por clock são atualizados juntos com =?
Suponha que D passe brevemente para 1 apenas entre duas bordas de subida e volte a 0 antes da borda seguinte. Q chega a valer 1? Explique usando a regra de amostragem e retenção.
Exercício 2-A — flip-flop D (reset em ação)
12 min
Construa um único flip-flop D na placa real. As conexões são SW[0] = D, KEY[0] = reset (ativo em nível baixo), LEDR[0] = Q e LEDR[1] = ~Q.
Leia isto antes de executar — é importante. A 50 MHz, Q acompanha D 50.000.000 de vezes por segundo, muito mais rápido do que seus olhos ou a câmera conseguem perceber. Portanto, embora este circuito realmente armazene D, você não consegue observar o armazenamento: ao mudar SW[0], parece que LEDR[0] acompanha a chave imediatamente. Por isso, este exercício demonstra principalmente o reset, e não o armazenamento. O comportamento que você observará é:
- Com
SW[0]para cima (D=1),LEDR[0]fica aceso, enquantoLEDR[1], que mostra~Q, fica apagado. - Mantenha
KEY[0]pressionado — o botão apresenta0, portanto o reset está ativo.LEDR[0]apaga mesmo queSW[0]continue para cima. O reset tem prioridade sobre os dados. - Solte
KEY[0]:LEDR[0]volta a acompanharSW[0].
Você verá a memória no próximo exercício, 2-B. Por enquanto, concentre-se no reset que substitui a entrada de dados.
// Licao 3 - Exercicio 2-A: modelo inicial de flip-flop D
// SW[0]=D, KEY[0]=reset (ativo em nivel baixo), LEDR[0]=Q, LEDR[1]=~Q.
module leds_mirror(CLOCK_50, SW, KEY, LEDR);
input CLOCK_50;
input [9:0] SW;
input [3:0] KEY;
output [9:0] LEDR;
reg Q;
always @(posedge CLOCK_50) begin
if (!KEY[0]) Q <= TODO_RESET_VALUE;
else Q <= TODO_D_INPUT;
end
assign LEDR[0] = TODO_Q_OUTPUT;
assign LEDR[1] = TODO_NOT_Q_OUTPUT;
assign LEDR[9:2] = 8'b0;
endmoduleAbra 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 flip-flop D acima. Mantenha o nome do módulo comoleds_mirror. Observe que ele agora declarainput CLOCK_50;: essa linha é obrigatória em qualquer projeto acionado por clock.Clique em Sintetizar (
Synthesize). Se um tour guiado cobrir o botão, feche ou pule o tour primeiro. Espere o processo terminar, o que leva cerca de 1 a 3 minutos. Se houver um erro, as causas mais comuns são a ausência deCLOCK_50na lista de portas ou de um ponto e vírgula. Corrija e sintetize novamente. Prossiga somente quando o estado final indicar uma compilação bem-sucedida com 0 erros.Clique em Enviar para a FPGA (
Upload to FPGA) e espere a sessão da placa e a câmera ao vivo.Coloque
SW[0]para cima (D=1). Confirme queLEDR[0](Q) está aceso eLEDR[1](~Q) está apagado.Demonstre o reset: com
SW[0]ainda para cima, mantenhaKEY[0]pressionado. ObserveLEDR[0]apagar enquanto a chave permanece igual: o reset substitui os dados. SolteKEY[0]e confirme queLEDR[0]volta a acender.
Ajuste SW[0] e KEY[0] como indicado em cada linha e registre o que LEDR[0] (Q) e LEDR[1] (~Q) mostram na câmera (1 = aceso, 0 = apagado). Lembre-se de que KEY é ativa em nível baixo: pressionado = 0, solto = 1.
| SW[0] (D) | KEY[0] (0=pressionado, 1=solto) | LEDR[0] = Q | LEDR[1] = ~Q |
|---|---|---|---|
Qual é a diferença entre este flip-flop e um fio simples de SW[0] para LEDR[0]? Responda em termos de memória.
Este projeto usa um reset síncrono. Aponte a linha de código que realiza o reset e explique o que “síncrono” significa quanto ao momento em que o reset entra em vigor. Você conhecerá o reset assíncrono na Lição 5.
Por que LEDR[1] sempre mostra o oposto de LEDR[0]?
O Exercício 2-A realmente armazena D, mas na câmera você só conseguiu ver o reset funcionar, nunca o armazenamento. Usando a frequência de 50 MHz, explique por que o armazenamento é invisível e o reset é claramente visível.
Exercício 2-B — registrador de carga paralela de 8 bits (memória visível)
12 min
Um registrador é simplesmente um conjunto de N flip-flops D lado a lado, todos compartilhando o mesmo clock e o mesmo reset. Um registrador de 8 bits armazena 8 bits. Uma carga paralela copia todos os 8 bits de entrada para o registrador ao mesmo tempo em uma borda do clock: cada flip-flop captura seu próprio bit de dados simultaneamente.
O recurso que torna a memória visível aqui é o controle de carga. Em vez de capturar os dados a cada borda, como fazia o flip-flop isolado, este registrador só captura quando você manda. Assim, você pode mudar as chaves à vontade e o valor armazenado permanece igual até que seja feita uma carga. As conexões são SW[7:0] = dados, KEY[0] = reset (ativo em nível baixo) e KEY[1] = carga (ativa em nível baixo).
Como KEY é ativa em nível baixo, você deve pressionar e soltar KEY[1] para carregar: ao pressionar, KEY[1] apresenta 0, e o código verifica !KEY[1] para detectar essa condição. Observe que KEY[1] é amostrada pelo clock; ela não é um sinal de clock com eliminação de rebotes em hardware. O projeto simplesmente verifica o nível do botão a cada borda de subida, o que é suficiente para este exercício.
// Licao 3 - Exercicio 2-B: modelo inicial de registrador de carga paralela de 8 bits
// SW[7:0]=dados, KEY[0]=reset (ativo em nivel baixo), KEY[1]=carga (ativa em nivel baixo).
module leds_mirror(CLOCK_50, SW, KEY, LEDR);
input CLOCK_50;
input [9:0] SW;
input [3:0] KEY;
output [9:0] LEDR;
reg [7:0] Q;
always @(posedge CLOCK_50) begin
if (!KEY[0]) Q <= TODO_RESET_VECTOR;
else if (!KEY[1]) Q <= TODO_LOAD_VALUE;
end
assign LEDR[7:0] = TODO_REGISTER_OUTPUT;
assign LEDR[9:8] = 2'b00;
endmoduleEm
leds_mirror.v, substitua todo o conteúdo pelo módulo do registrador de 8 bits acima. Mantenha o nome do módulo comoleds_mirrore preserveinput CLOCK_50;.Clique em Sintetizar (
Synthesize), espere o sucesso e depois clique em Enviar para a FPGA (Upload to FPGA).Ajuste as chaves para
SW[7:0] = 10101010, sendoSW[7]o bit mais significativo. Os LEDs ainda não mostrarão esse valor: nada foi carregado.Carregue: pressione e solte
KEY[1].LEDR[7:0]agora mostra10101010. O registrador capturou as chaves.Veja a memória: agora mude as chaves para
SW[7:0] = 01010101sem tocar emKEY[1]. Observe os LEDs: eles não mudam. O registrador mantém o valor armazenado, embora as entradas sejam diferentes. Isso é memória.Atualize: pressione e solte
KEY[1]novamente. AgoraLEDR[7:0]muda para01010101.Reinicie: pressione e solte
KEY[0]. Todos os oito LEDs apagam —Q = 00000000— independentemente das chaves.
Execute a sequência de carga e registre os 8 LEDs (LEDR[7:0]) em cada etapa. A observação principal é a linha 'chaves alteradas, sem carga': o valor armazenado deve permanecer igual ao da linha anterior.
| Etapa | Valor de SW[7:0] | Ação em KEY | LEDR[7:0] observado |
|---|---|---|---|
Envie sua evidência. Faça uma captura da câmera que prove que o registrador está mantendo o valor: LEDR[7:0] mostra 10101010, o valor carregado anteriormente, enquanto as chaves na tela SW[7:0] estão em um padrão diferente, como 01010101, e você não pressionou KEY[1]. Mostre na mesma captura os LEDs mantidos e as posições diferentes das chaves e anexe-a abaixo.
Na etapa 5, você mudou as chaves, mas os LEDs não se alteraram. Explique por quê usando as palavras “armazenar” e “carregar”.
Se você mantivesse reset (KEY[0]) e carga (KEY[1]) pressionados ao mesmo tempo em uma borda do clock, qual valor o registrador assumiria e por quê? Aponte a linha de código que decide isso.
Qual é a finalidade prática de um registrador de carga paralela dentro de uma CPU? Dê um exemplo concreto do que esse registrador pode armazenar.
O que você construiu e uma observação sobre estilos de reset
8 min
Você construiu os dois fundamentos de toda memória em sistemas digitais: o flip-flop D, que armazena um bit por amostragem e retenção na borda do clock, e o registrador, formado por vários flip-flops carregados em paralelo que mantêm o valor até receberem outra instrução. Você usou always @(posedge CLOCK_50) com a atribuição não bloqueante <= e viu uma entrada KEY ativa em nível baixo funcionar como controle.
Uma observação para as próximas lições — dois estilos de reset. Os dois projetos desta lição usaram um reset síncrono: ele foi verificado dentro do bloco acionado por clock, portanto só entrou em vigor em uma borda de subida. Isso é simples e previsível, e continuará sendo usado nos elementos de armazenamento desta lição e da próxima. As lições de máquinas de estados finitos, 5 e 6, porém, passarão a usar um reset assíncrono ativo em nível baixo, escrito como always @(posedge CLOCK_50 or negedge KEY[0]), que força o circuito a um estado seguro imediatamente, sem esperar a borda do clock. Isso é útil para recuperar uma máquina de estados travada na placa. A diferença detalhada e a razão para as FSMs preferirem o estilo assíncrono serão explicadas lá. Por enquanto, basta saber que os dois estilos existem e que esta lição usou deliberadamente o síncrono.
Qual sequência corresponde ao que você fez em cada exercício desta lição?
Em três ou quatro frases, explique como o Exercício 2-B comprovou que o registrador tem memória — cite seus dados —, por que o Exercício 2-A só conseguiu demonstrar o reset e não o armazenamento e indique um momento em que a polaridade ativa em nível baixo de KEY foi importante.
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