Teach lesson
Lógica digital na DE1-SoC (6/6): projeto final de FSM — máquina de venda automática
Projete, sintetize e amplie uma FSM de máquina de venda automática na DE1-SoC, testando moedas, reset, liberação, troco e crédito nos displays.
New to LabsLand? Create your teacher account
Learning Outcomes
Projetar e ampliar uma máquina de estados finitos não trivial em hardware real.
Construir um controlador de máquina de venda cujo estado represente o crédito acumulado.
Adicionar recursos em checkpoints guiados: devolução de troco, seleção de produto e display decimal.
Raciocinar sobre o número de estados e a quantidade de flip-flops necessária.
Student activity preview
Activity Content
Preview only. In a class session, students can fill in responses and submit their work to the teacher.
Do detector ao controlador: uma FSM que guarda seu dinheiro
8 min
Na Lição 5, você construiu máquinas de estados finitos cujo estado era uma posição abstrata em um reconhecedor: um semáforo que percorria verde → amarelo → vermelho e um detector de sequência 1011 que lembrava quanto do padrão já havia sido visto. Neste projeto final, o estado representa algo concreto e físico: quanto dinheiro foi inserido até agora. Você construirá um pequeno controlador de máquina de venda automática e depois o ampliará, recurso por recurso.
Lembre-se de que uma máquina de estados finitos possui três partes: um conjunto de estados, uma regra para o próximo estado a partir do estado atual e das entradas e uma saída que depende do estado — e, em uma máquina de Mealy, também das entradas. A habilidade nova desta lição é o projeto: primeiro você decide quais estados são necessários e desenha a tabela de transições; só depois escreve o Verilog.
Para uma máquina que recebe moedas, a escolha natural é fazer cada estado representar um nível de crédito. Se ela aceita moedas de 5 e 10 centavos e libera o produto ao chegar a 15 centavos, o crédito pode ser 0, 5, 10 ou 15 centavos — quatro estados. Vamos chamá-los de S0, S5, S10 e S15. Inserir uma moeda leva a um estado de crédito maior; alcançar o preço libera o produto e devolve a máquina a S0.
Os sinais da placa são os mesmos de sempre. Como este é um projeto sequencial, acionado por clock, o próprio módulo declara o clock: o modelo padrão leds_mirror.v não possui clock, portanto acrescentamos input CLOCK_50;. Como na Lição 5, uma chave levantada vale 1; KEY é ativo em nível baixo, portanto um botão pressionado vale 0; LEDR é ativo em nível alto; e HEX0–HEX5 são displays de sete segmentos ativos em nível baixo.
A máquina de venda como diagrama de estados de crédito. Uma moeda de 5 centavos, SW[0], avança um estado; uma moeda de 10 centavos, SW[1], avança dois. Alcançar o preço ativa a saída de liberação e retorna a S0; KEY[0] reinicia qualquer estado em S0. Os checkpoints posteriores acrescentam uma segunda saída, devolução de troco, e mais estados para a seleção de produto.
A máquina básica usa quatro estados separados: S0, S5, S10, S15. Por que ela precisa de um estado diferente para cada nível de crédito? O que ela *não* conseguiria fazer se tivesse apenas um estado?
LEDR[7], liberação, fica retido e permanece aceso até você pressionar KEY[0]. Depois que o produto é liberado e a máquina volta a S0, o que apaga essa retenção? Que problema surgiria se nada a apagasse?
Exercício 3-C: FSM da máquina de venda automática
14 min
Este é o projeto inicial. Leia-o como uma FSM nos três blocos que agora já são familiares: um divisor de clock, para que a inserção de moedas ocorra em um ritmo humano; um bloco combinacional de próximo estado e saída; e um registrador de estado acionado por clock, com reset assíncrono ativo em nível baixo. Preencha os marcadores TODO_* antes de sintetizar.
Os estados. S0, S5, S10, S15 são os quatro níveis de crédito, codificados como valores de 2 bits, 0..3. O crédito atual em centavos é derivado do estado e mostrado de duas formas: em LEDR[3:0], como um número binário de centavos, e em HEX0, como um único caractere: 0 para 0 centavos, 5 para 5, A para 10 e F para 15. Esses são simplesmente os dígitos hexadecimais de valor 0, 5, 10 e 15.
As transições — esta é a tabela que você desenharia primeiro ao projetar:
Resumo das transições:
- S0, 0 centavos: moeda de 5 -> S5; moeda de 10 -> S10; não libera.
- S5, 5 centavos: moeda de 5 -> S10; moeda de 10 -> S15; não libera.
- S10, 10 centavos: moeda de 5 -> S15; moeda de 10 -> S0 e libera ao chegar a 20 centavos, sem troco na versão básica.
- S15, 15 centavos: no pulso seguinte -> S0 e libera, independentemente da entrada.
Dois detalhes merecem atenção. Em S10, inserir uma moeda de 10 centavos chega a 20 — mais do que o preço de 15 — portanto a máquina libera o produto e volta a S0, mas nesta versão básica não devolve troco. O checkpoint seguinte corrige isso. E S15 é um estado de “liberar e reiniciar”: no pulso seguinte, ele ativa dispense e volta a S0, independentemente da entrada. Portanto, quando as moedas totalizam exatamente 15 centavos, você verá o crédito chegar a F; no pulso seguinte, o produto será liberado.
A saída de liberação é retida. dispense é um sinal de um pulso. Como no detector da Lição 5, é fácil perder um pulso único na câmera remota. Por isso, nós o guardamos em dispense_latch, ligamos esse valor a LEDR[7] e o apagamos apenas no reset. Assim, depois da liberação, LEDR[7] permanece aceso até você pressionar KEY[0].
Inserção de moeda — temporização. O divisor usa DIV = 16,666,666, portanto slow_clk alterna aproximadamente a cada 0,33 s. Para inserir uma moeda, levante uma chave, espere um pulso do clock lento, cerca de um terço de segundo, e abaixe-a novamente para 0. Manter uma chave levantada por vários pulsos contaria como várias moedas. Insira uma moeda por vez.
// Lição 6 - Exercício 3-C: módulo inicial da FSM da máquina de venda
// Complete as transições de crédito, a retenção de liberação e a lógica de saída antes de sintetizar.
module leds_mirror(CLOCK_50, SW, KEY, LEDR, HEX0);
input CLOCK_50;
input [9:0] SW;
input [3:0] KEY;
output [9:0] LEDR;
output [6:0] HEX0;
localparam S0 = 2'd0, S5 = 2'd1, S10 = 2'd2, S15 = 2'd3;
localparam [24:0] DIV = 25'd16_666_666;
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
wire nickel = SW[0];
wire dime = SW[1];
reg [1:0] state, next_state;
reg dispense_latch;
always @(posedge slow_clk or negedge KEY[0]) begin
if (!KEY[0]) begin
state <= S0;
dispense_latch <= 1'b0;
end else begin
state <= next_state;
if (TODO_DISPENSE_CONDITION) dispense_latch <= 1'b1;
end
end
always @(*) begin
case (state)
S0: next_state = TODO_FROM_S0;
S5: next_state = TODO_FROM_S5;
S10: next_state = TODO_FROM_S10;
S15: next_state = TODO_FROM_S15;
default: next_state = S0;
endcase
end
assign LEDR[3:0] = TODO_CREDIT_LEDS;
assign LEDR[6:4] = 3'b000;
assign LEDR[7] = TODO_DISPENSE_LED;
assign LEDR[9:8] = 2'b00;
assign HEX0 = TODO_CREDIT_HEX_PATTERN;
endmodulePartindo de S0, preveja antes de executar: que sequência de caracteres aparecerá em HEX0 e quando LEDR[7], liberação, acenderá se você inserir uma moeda de 10 centavos e depois uma de 5, uma por pulso?
Execute a máquina: moedas, liberação e reset
14 min
Construa o controlador na placa real, insira moedas por dois caminhos diferentes e confirme que o LED de liberação permanece aceso.
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 acima. Mantenha o nomeleds_mirror. Observe que ele declarainput CLOCK_50;: o modelo não acrescenta o clock automaticamente.Selecione Sintetizar (
Synthesize). Se um tour guiado cobrir o botão, feche-o ou pule-o. Espere de 1 a 3 minutos, corrija qualquer erro de digitação e sintetize outra vez. Continue apenas quando a compilação terminar com 0 erros.Selecione Enviar para a FPGA (
Upload to FPGA) e aguarde a placa ao vivo e a câmera.Pressione e solte
KEY[0]para reiniciar.HEX0deve mostrar0eLEDR[7]deve estar apagado.Três moedas de 5. Levante
SW[0], espere cerca de um terço de segundo por um pulso e abaixeSW[0].HEX0passa a5. Repita para chegar aA, 10 centavos, e mais uma vez para chegar aF, 15. No pulso seguinte, o produto é liberado:LEDR[7]acende eHEX0volta a0.LEDR[7]permanece aceso.Pressione
KEY[0]para reiniciar:LEDR[7]apaga eHEX0mostra0.Moeda de 5 + moeda de 5 + moeda de 10. Insira duas moedas de 5 (
HEX0:0→5→A) e depois uma de 10 emSW[1]. A partir de S10, ela leva o total a 20 centavos; a máquina libera imediatamente e volta a S0.LEDR[7]acende.Pressione
KEY[0]para reiniciar antes do checkpoint seguinte.
Comece pelo reset. Insira uma moeda por pulso e, depois de cada uma, registre o caractere em HEX0, que representa o crédito, e se LEDR[7], liberação, está aceso. Faça primeiro o caminho de três moedas de 5; depois reinicie e faça o caminho de duas moedas de 5 mais uma de 10.
| Moeda inserida | Crédito em HEX0 (0/5/A/F) | LEDR[7] liberou? (sim/não) |
|---|---|---|
Envie sua evidência. Depois que três moedas de 5 fizerem a máquina liberar o produto, faça uma captura da câmera com LEDR[7] aceso e anexe-a abaixo.
Quando o crédito chega a 15 centavos e HEX0 mostra F, o produto não é liberado no mesmo pulso: isso acontece no pulso seguinte. Observando o caso S15 no código, explique em uma frase por quê.
No projeto básico, inserir uma moeda de 10 a partir de S10 chega a 20 centavos e libera o produto, mas o cliente perde 5 centavos. Que saída você precisa acrescentar para corrigir isso e em qual único caso ela se aplica?
Checkpoint 3-C1 (obrigatório): acrescente a devolução de troco
12 min
Sua primeira ampliação corrige o caso injusto que você acabou de identificar. Quando a máquina chega a 20 centavos — o único caminho com pagamento a mais, S10 seguido de uma moeda de 10 —, ela deve acender um segundo LED, LEDR[6], com o significado de “devolver 5 centavos de troco”, além de liberar o produto em LEDR[7]. Nenhuma outra liberação, exatamente a 15 centavos, devolve troco.
Esta é uma alteração pequena e precisa na FSM. Você precisa de uma segunda saída retida, change_latch, ligada a LEDR[6] e ativada sempre que a máquina liberar o produto *pelo caminho de 20 centavos*. A forma mais limpa é acrescentar um sinal combinacional change ao lado de dispense, ativá-lo somente no ramo S10 + moeda de 10 e retê-lo exatamente como dispensed_latch.
Este é o comportamento esperado após sua alteração:
Comportamento esperado no checkpoint:
- Três moedas de 5 (5+5+5): o crédito chega a 15 centavos; LEDR[7], liberação = sim; LEDR[6], troco = não; retorna a S0.
- Moeda de 5 + moeda de 10 (5+10): o crédito chega a 15 centavos; LEDR[7], liberação = sim; LEDR[6], troco = não; retorna a S0.
- Moeda de 10 + moeda de 5 (10+5): o crédito chega a 15 centavos; LEDR[7], liberação = sim; LEDR[6], troco = não; retorna a S0.
- Moeda de 5 + moeda de 5 + moeda de 10 (5+5+10): o crédito chega a 20 centavos; LEDR[7], liberação = sim; LEDR[6], troco = sim; retorna a S0.
- Duas moedas de 10 (10+10): o crédito chega a 20 centavos; LEDR[7], liberação = sim; LEDR[6], troco = sim; retorna a S0.
Implemente a alteração e confirme na placa que LEDR[6] acende somente em um caminho de 20 centavos e nunca em um caminho de exatamente 15 centavos.
Edite seu módulo da máquina de venda para acrescentar a saída de devolução de troco: um
LEDR[6]retido que acende somente quando a máquina libera o produto pelo caminho de 20 centavos, S10 + moeda de 10, junto com a liberação já existente emLEDR[7].Selecione Sintetizar (
Synthesize), corrija qualquer erro de digitação e selecione Enviar para a FPGA (Upload to FPGA).Reinicie. Execute um caminho de exatamente 15 centavos, por exemplo três moedas de 5: confirme que
LEDR[7]acende eLEDR[6]permanece apagado.Reinicie. Execute um caminho de 20 centavos, duas moedas de 5 seguidas de uma de 10 ou duas moedas de 10: confirme que ambos,
LEDR[7]eLEDR[6], acendem.
Depois da alteração 3-C1, execute cada caminho e leia os dois LEDs de saída. Registre se LEDR[7], liberação, e LEDR[6], troco, estão acesos ao final de cada caminho.
| Caminho de moedas | Crédito alcançado (15/20 centavos) | LEDR[7] liberou? (sim/não) | LEDR[6] devolveu troco? (sim/não) |
|---|---|---|---|
Envie sua evidência. Em um caminho de 20 centavos, por exemplo duas moedas de 5 seguidas de uma de 10, faça uma captura da câmera mostrando ambos LEDR[7], liberação, e LEDR[6], troco, acesos e anexe-a abaixo.
Capture o código do laboratório. Salve o leds_mirror.v modificado no laboratório e use o controle de evidência de código abaixo para enviar a versão do checkpoint 3-C1 com devolução de troco. Se o professor pedir um diagrama de estados, envie-o separadamente.
Na sua FSM modificada, em qual caso exato de estado e entrada a saída de troco, LEDR[6], é ativada? Por que esse é o *único* caso de pagamento a mais nesta máquina?
Checkpoint 3-C2 (opcional / bônus): seleção de produto
8 min
Este checkpoint bônus faz a máquina vender dois produtos: um refrigerante por 15 centavos, selecionado com SW[2], e um café por 20 centavos, selecionado com SW[3]. A máquina deve liberar o produto somente quando o crédito alcançar o preço do item selecionado: com refrigerante, libera a 15 centavos; com café, espera até 20.
A ideia importante é que isso aumenta o espaço de estados. A máquina já não pode ser descrita apenas pelo crédito, pois seu comportamento a, por exemplo, 15 centavos agora depende de *qual produto* foi selecionado: libera se for refrigerante e continua esperando se for café. A máquina precisa lembrar tanto o crédito quanto a seleção. Combine os dois — por exemplo, um estado pode ser o par *(produto selecionado, nível de crédito)* — e determine quais combinações podem realmente ocorrer e o que cada uma faz.
Desenhe primeiro o diagrama de estados ampliado, decida quantos estados são realmente necessários e só então implemente. Não existe uma única codificação “correta”, mas existe um número mínimo de estados distintos exigido pelo comportamento e, a partir dele, um número mínimo de flip-flops.
(Bônus) Envie seu projeto. Anexe o diagrama de estados ampliado da máquina de dois produtos e cole o Verilog modificado ou anexe o código como arquivo .txt.
(Bônus) De quantos estados distintos sua máquina de dois produtos precisa e, portanto, qual é o número mínimo de flip-flops necessário? Lembre-se de que o número mínimo de flip-flops para codificar N estados é ceil(log2(N)). Informe seu N, mostre o cálculo ceil(log2(N)) e justifique brevemente sua contagem de estados.
Checkpoint 3-C3 (opcional / bônus): display decimal de dois dígitos
7 min
A máquina básica mostra o crédito como um único caractere hexadecimal, 0, 5, A ou F. É compacto, mas não é assim que uma máquina real mostra dinheiro. Este checkpoint bônus exibe o crédito como um número decimal de dois dígitos em HEX1:HEX0: 00, 05, 10, 15 e, com os caminhos 3-C1 / 3-C2, 20. HEX0 mostra as unidades e HEX1, as dezenas.
Isso reutiliza a ideia do decodificador de sete segmentos da Lição 2: uma função seg que associa um único dígito decimal de 0..9 ao padrão de segmentos ativo em nível baixo {g,f,e,d,c,b,a}. Separe o crédito em dezenas e unidades, decodifique cada parte e controle HEX1 e HEX0. Lembre-se de acrescentar output [6:0] HEX1; à lista de portas do módulo: o modelo padrão expõe apenas HEX0, portanto HEX1 precisa ser declarado, exatamente como você fez no decodificador de dois dígitos da Lição 2.
Uma função decimal seg, no mesmo formato da Lição 2, e a separação dos valores de crédito que você realmente usa, 0, 5, 10, 15 e 20, são suficientes:
output [6:0] HEX1; // acrescente à lista de portas (dígito das dezenas)
// ...
function [6:0] seg; // dígito decimal 0..9 -> segmentos ativos em nível baixo
input [3:0] d;
case (d)
4'd0: seg = 7'b1000000;
4'd1: seg = TODO_SEGMENT_PATTERN_1;
4'd2: seg = TODO_SEGMENT_PATTERN_2;
// Continue de 3 a 9; no default, apague o display.
default: seg = 7'b1111111;
endcase
endfunction
wire [4:0] price = 5'd15;
wire [4:0] credit = TODO_CURRENT_CREDIT_VALUE;
wire [4:0] change = TODO_CHANGE_VALUE;
wire [3:0] tens = TODO_TENS_DIGIT;
wire [3:0] ones = TODO_ONES_DIGIT;
assign HEX0 = seg(ones);
assign HEX1 = seg(tens);(Bônus) Edite seu módulo da máquina de venda: acrescente
output [6:0] HEX1;à lista de portas, adicione um decodificador decimalseg, separe o crédito em dezenas e unidades e controleHEX1:HEX0.Selecione Sintetizar (
Synthesize), corrija qualquer erro de digitação e selecione Enviar para a FPGA (Upload to FPGA).Reinicie e insira moedas. Confirme que
HEX1:HEX0mostra00,05,10,15e20em um caminho com pagamento a mais à medida que o crédito aumenta.
(Bônus) Envie sua evidência. Faça uma captura da câmera com HEX1:HEX0 mostrando um crédito decimal de dois dígitos, por exemplo 10 ou 15, e anexe-a abaixo.
(Bônus) Para um crédito de 15 centavos, que dígitos de dezena e unidade o decodificador calcula, os valores que alimentam HEX1 e HEX0, e qual display mostra 5?
Fechamento do projeto final: o que você construiu ao longo do curso
12 min
Você construiu em hardware real um percurso completo pela lógica digital:
- Portas lógicas (Lição 1): AND, OR, NOT, NAND, NOR e XOR — os átomos de todo circuito — e a ideia de que Verilog *descreve hardware*, em vez de ser executado como um programa.
- Projeto combinacional e minimização (Lição 2): tabelas-verdade, soma de produtos, produto de somas, mapas de Karnaugh e um multiplexador — a passagem de uma especificação para o circuito *mais simples* que a atende.
- Memória (Lição 3): o flip-flop e o registrador de carga paralela — o momento em que um circuito ganha a capacidade de *lembrar*.
- Contadores e registradores de deslocamento (Lição 4): blocos sequenciais acionados por um divisor de clock, mais a disciplina de conferir se uma constante cabe no registrador e se a ordem dos bits corresponde ao que você vê.
- Máquinas de estados finitos (Lição 5): máquinas de Moore e Mealy — um semáforo e um detector de sequência — que agem sobre um estado lembrado.
- Um projeto final de FSM (esta lição): um controlador de máquina de venda automática que você *projetou* a partir de uma tabela de transições e depois *ampliou* — devolução de troco, seleção de produto e display decimal —, raciocinando ao longo do caminho sobre a quantidade de estados e flip-flops.
Esse é o núcleo de um curso introdutório de lógica digital, e você executou cada parte em uma FPGA real.
Próximos caminhos. Algumas possibilidades, todas opcionais, caso queira continuar:
- Temporização — setup e hold. Flip-flops reais exigem que a entrada permaneça estável por uma pequena janela ao redor da borda do clock. Respeitar esses tempos de *setup* e *hold* permite que um projeto funcione na frequência de clock desejada e abre a porta para a análise estática de temporização.
- Aritmética e ALUs. Combine somadores, comparadores e lógica em uma unidade lógica e aritmética, o coração computacional de um processador.
- Pipeline. Divida um caminho combinacional longo em estágios com clock para aumentar a vazão, uma técnica central em hardware rápido.
- Outras HDLs e simulação. SystemVerilog e VHDL ampliam o que você viu; a *simulação*, com testbenches e formas de onda, permite verificar um projeto antes mesmo de chegar à placa.
Leitura adicional opcional. Os cursos gratuitos da [FPGA Academy](https://fpgacademy.org/courses.html) abordam em profundidade essa mesma família DE1-SoC, e Harris & Harris, *Digital Design and Computer Architecture*, capítulos 1–3, é um excelente texto para o conteúdo de portas lógicas a FSMs que você acabou de estudar. Nenhum deles é obrigatório: estão aqui caso queira se aprofundar.
Em algumas frases, descreva como a ideia de *memória* cresceu ao longo do curso: de um único flip-flop, na Lição 3, a um registrador e um contador, nas Lições 3–4, e então a uma máquina de estados finitos cujo estado tinha significado, nas Lições 5–6. O que a inclusão de memória permitiu que seus circuitos fizessem que portas puramente combinacionais não podiam fazer?
Em três frases, resuma seu projeto final: (1) o que o *estado* da máquina de venda representava e como uma moeda o alterava; (2) um recurso que você acrescentou em um checkpoint e a evidência que registrou; e (3) uma situação em que o comportamento ativo em nível baixo ou a retenção de uma saída foi importante para enxergar o resultado na câmera.