Lección Teach
Lógica digital en la DE1-SoC (6/6): capstone de FSM, una máquina expendedora
El alumnado sintetiza y sube a la DE1-SoC una FSM de máquina expendedora en Verilog, y prueba interruptores, reset KEY, salida LEDR y crédito en HEX.
Resultados de aprendizaje
Diseñar y ampliar una máquina de estados finitos no trivial en hardware real.
Construir un controlador de máquina expendedora cuyo estado es el crédito acumulado.
Añadir funciones en puntos de control: devolución de cambio, selección de producto y display decimal.
Razonar cuántos estados necesita un diseño y cuántos biestables hacen falta para codificarlos.
Vista previa de la actividad del estudiante
Contenido de la actividad
Solo vista previa. En una sesión de clase, los estudiantes pueden completar respuestas y entregar su trabajo al docente.
Del detector al controlador: una FSM que guarda tu dinero
8 min
En la Lección 5 construiste máquinas de estados finitos cuyo estado era una posición abstracta dentro de un reconocedor: un semáforo que recorría verde -> amarillo -> rojo y un detector de "1011" que recordaba cuánto del patrón había visto. En este capstone, el estado significa algo concreto y físico: cuánto dinero se ha introducido hasta ahora. Vas a construir una pequeña máquina expendedora y después ampliarla función por función.
Recuerda que una máquina de estados finitos tiene tres partes: un conjunto de estados, una regla de siguiente estado dada la situación actual y las entradas, y una salida que depende del estado (y, en una máquina Mealy, también de las entradas). La habilidad nueva de esta lección es el diseño: decides cuáles deben ser los estados, dibujas la tabla de transiciones y solo después escribes Verilog.
Para una máquina de monedas, lo natural es que cada estado *sea* un nivel de crédito. Si la máquina acepta monedas de 5 y 10 céntimos y entrega el producto a 15 céntimos, el crédito puede ser 0, 5, 10 o 15 céntimos: cuatro estados. Los llamaremos S0, S5, S10, S15. Insertar una moneda te mueve a un estado de más crédito; alcanzar el precio dispensa el producto y vuelve a S0.
Las señales de la placa son las de siempre. Como este diseño tiene reloj, el módulo declara el reloj: la plantilla inicial de leds_mirror.v no tiene reloj, así que añadimos input CLOCK_50;. Como en la Lección 5, un interruptor arriba es lógica 1, KEY es activo en bajo (pulsado lee 0), LEDR es activo en alto (encendido con 1) y HEX0-HEX5 son displays de 7 segmentos activos en bajo (un segmento se enciende con 0).
La máquina expendedora como diagrama de estados de crédito. Cada estado es un nivel de crédito. Una moneda de 5c (SW[0]) avanza un paso; una de 10c (SW[1]) avanza dos. Al alcanzar el precio se activa dispense y se vuelve a S0; KEY[0] resetea cualquier estado a S0. Los puntos de control posteriores añaden devolución de cambio y selección de producto.
La máquina base usa cuatro estados separados S0, S5, S10, S15. Por qué necesita un estado distinto para cada nivel de crédito en vez de un único estado? Qué no podría hacer la máquina si solo tuviera un estado?
LEDR[7] (dispense) está enclavado y permanece encendido hasta pulsar KEY[0]. Después de dispensar y volver a S0, qué limpia el latch, y qué problema habría si nada lo limpiara nunca?
Ejercicio 3-C: la FSM de la máquina expendedora
14 min
Este es el diseño base completo. Léelo como una máquina de estados con los bloques ya conocidos: un divisor de reloj para que las monedas se registren a ritmo humano, un bloque combinacional de siguiente estado/salida, y un registro de estado con reloj y reset asíncrono activo en bajo.
Estados. S0, S5, S10, S15 son los cuatro niveles de crédito, codificados como valores de 2 bits 0..3. El crédito actual en céntimos se deriva del estado y se muestra de dos formas: en LEDR[3:0] como número binario de céntimos, y en HEX0 como un carácter: 0 para 0c, 5 para 5c, A para 10c y F para 15c (son los dígitos hexadecimales cuyos valores son 0, 5, 10 y 15).
Transiciones (esta es la tabla que dibujarías primero al diseñar):
Resumen de transiciones:
- S0 (0c): moneda de 5 -> S5; moneda de 10 -> S10; sin dispensar.
- S5 (5c): moneda de 5 -> S10; moneda de 10 -> S15; sin dispensar.
- S10 (10c): moneda de 5 -> S15; moneda de 10 -> S0 y dispensa a 20c, sin cambio en la versión base.
- S15 (15c): en el siguiente tick -> S0 y dispensa, independientemente de la entrada.
Dos detalles importan. Desde S10, insertar una moneda de 10 llega a 20 céntimos, más que el precio de 15c; por tanto la máquina dispensa y vuelve a S0, pero en la versión base no devuelve cambio. El checkpoint 3-C1 lo corrige. Además, S15 es un estado de "dispensar y resetear": en el siguiente tick activa dispense y vuelve a S0, sin importar las entradas. Así que si las monedas llegan exactamente a 15c, verás el crédito llegar a F y en el tick siguiente se dispensará.
La salida dispense está enclavada. dispense en sí dura un tick. Como en el detector de la Lección 5, un pulso de un tick es fácil de perder en una cámara remota; por eso lo enclavamos en dispensed_latch, lo llevamos a LEDR[7] y solo lo limpiamos con reset. Así, cuando la máquina ha dispensado, LEDR[7] se queda encendido hasta que pulses KEY[0].
Insertar una moneda: temporización. El divisor usa DIV = 16,666,666, así que slow_clk cambia aproximadamente cada 0,33 s. Para insertar una moneda debes subir un interruptor, esperar un tick de slow-clock (aprox. un tercio de segundo) y volverlo a bajar. Si mantienes el interruptor arriba durante varios ticks, se contará como varias monedas. Inserta monedas de una en una.
// Lección 6 - Ejercicio 3-C: FSM de máquina expendedora (base)
// SW[0]=moneda 5c, SW[1]=moneda 10c, KEY[0]=reset (activo en bajo).
// LEDR[7]=dispense (ENCLAVADO hasta reset). LEDR[3:0]=crédito en céntimos. HEX0=crédito (0,5,A=10,F=15).
// Insertar moneda: subir un switch, esperar un tick slow_clk (~0.33 s), bajarlo.
// Dispensa al llegar a 15c (o a 20c desde S10+dime); sin cambio en esta versión base.
module leds_mirror(CLOCK_50, KEY, SW, LEDR, HEX0);
input CLOCK_50;
input [3:0] KEY; // KEY[0] = reset_n
input [9:0] SW; // SW[0]=5c, SW[1]=10c
output [9:0] LEDR;
output [6:0] HEX0;
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
localparam S0=2'd0, S5=2'd1, S10=2'd2, S15=2'd3; // niveles de crédito
reg [1:0] state, next_state;
reg dispense;
always @(*) begin
next_state = state;
dispense = 1'b0;
case (state)
S0: if (SW[0]) next_state = S5; else if (SW[1]) next_state = S10;
S5: if (SW[0]) next_state = S10; else if (SW[1]) next_state = S15;
S10: if (SW[0]) next_state = S15;
else if (SW[1]) begin next_state = S0; dispense = 1'b1; end // 20c -> dispense, sin cambio
S15: begin next_state = S0; dispense = 1'b1; end
default: next_state = S0;
endcase
end
reg dispensed_latch;
always @(posedge slow_clk or negedge KEY[0]) begin
if (!KEY[0]) begin state <= S0; dispensed_latch <= 1'b0; end
else begin
state <= next_state;
if (dispense) dispensed_latch <= 1'b1;
end
end
wire [3:0] credit = (state==S0) ? 4'd0 : (state==S5) ? 4'd5 : (state==S10) ? 4'd10 : 4'd15;
assign LEDR[7] = dispensed_latch;
assign LEDR[3:0] = credit;
assign LEDR[6:4] = 3'b0;
assign LEDR[9:8] = 2'b0;
function [6:0] seg;
input [3:0] d;
case (d)
4'd0: seg = 7'b1000000; // 0
4'd5: seg = 7'b0010010; // 5
4'd10: seg = 7'b0001000; // A (= 10)
4'd15: seg = 7'b0001110; // F (= 15)
default: seg = 7'b1111111;
endcase
endfunction
assign HEX0 = seg(credit);
endmodulePartiendo de S0, predice antes de ejecutarlo la secuencia de caracteres que verás en HEX0 y cuándo se encenderá LEDR[7] (dispense) si insertas una moneda de 10c y luego una de 5c (una por tick).
Ejecuta la expendedora: monedas, dispense y reset
14 min
Construye el controlador en la placa real, introduce monedas por dos caminos y confirma que el LED de dispense se enclava.
Abre el laboratorio Verilog de la DE1-SoC. El editor muestra
leds_mirror.v.Selecciona todo el contenido de
leds_mirror.vy reemplázalo por el módulo de la expendedora. Mantén el nombreleds_mirror. Observa que declarainput CLOCK_50;: la plantilla no añade el reloj.Pulsa Sintetizar. Si una guía tapa el botón, ciérrala o sáltala primero. Espera a que termine (aprox. 1-3 minutos). Corrige cualquier error y vuelve a sintetizar. Continúa solo cuando el estado final diga 0 errores.
Pulsa Enviar a la FPGA y espera a la placa y la cámara.
Pulsa y suelta
KEY[0]para resetear.HEX0debe mostrar0yLEDR[7]debe estar apagado.Tres monedas de 5c. Inserta una moneda de 5c: sube
SW[0], espera un tercio de segundo para un tick y vuelve a bajarlo.HEX0pasa a5. Repite para llegar aA(10c), y una vez más: el crédito llega aF(15c) y en el siguiente tick se dispensa el producto.LEDR[7]se enciende yHEX0vuelve a0.LEDR[7]queda encendido.Pulsa
KEY[0]para resetear (LEDR[7]se apaga yHEX0muestra0).5c + 5c + 10c. Inserta dos monedas de 5c (
HEX0:0->5->A), y luego una moneda de 10c (SW[1]): desde S10, una de 10c llega a 20c, por lo que la máquina dispensa inmediatamente y vuelve a S0.LEDR[7]se enciende.Pulsa
KEY[0]para resetear antes del siguiente checkpoint.
Resetea primero. Inserta monedas una por tick y registra, después de cada moneda, el caracter de HEX0 (crédito) y si LEDR[7] (dispense) está encendido. Haz primero el camino de tres monedas de 5c; luego resetea y haz 5c+5c+10c.
| Moneda insertada | Credito HEX0 (0/5/A/F) | LEDR[7] dispense? (si/no) |
|---|---|---|
Sube tu evidencia. Después de que tres monedas de 5c hagan dispensar la máquina, captura una imagen de la cámara con LEDR[7] encendido y adjúntala abajo.
Cuando el crédito llega a 15c (HEX0 muestra F), el producto no se dispensa en ese mismo tick: se dispensa en el siguiente. Mirando el caso S15 del código, explica en una frase por qué.
En el diseño base, insertar una moneda de 10c desde S10 llega a 20c y dispensa, pero el cliente pierde 5c. Qué salida tendrías que añadir para corregirlo, y en qué único caso se aplica?
Checkpoint 3-C1 (obligatorio): añadir devolución de cambio
12 min
Tu primera extensión corrige el caso injusto. Cuando la máquina llega a 20 céntimos, el único camino con sobrepago (S10 seguido de moneda de 10c), debe encender un segundo LED, LEDR[6], que significa "devolver 5c de cambio", además de dispensar en LEDR[7]. En todas las demás dispensaciones, exactamente 15c, no hay cambio.
Es un cambio pequeño en la FSM. Necesitas una segunda salida enclavada, change_latch, llevada a LEDR[6], que se active cuando la máquina dispensa *desde el camino de 20c*. La forma más limpia es añadir una señal combinacional change junto a dispense, activarla solo en la rama S10 + moneda de 10c, y enclavarla igual que dispensed_latch.
Comportamiento esperado:
- Tres monedas de 5c (5+5+5): crédito 15c, LEDR[7] dispense = sí, LEDR[6] cambio = no, volver a S0.
- 5c + 10c: crédito 15c, LEDR[7] = sí, LEDR[6] = no, volver a S0.
- 10c + 5c: crédito 15c, LEDR[7] = sí, LEDR[6] = no, volver a S0.
- 5c + 5c + 10c: crédito 20c, LEDR[7] = sí, LEDR[6] = sí, volver a S0.
- Dos monedas de 10c (10+10): crédito 20c, LEDR[7] = sí, LEDR[6] = sí, volver a S0.
Impleméntalo y verifica en la placa que LEDR[6] se enciende solo en un camino de 20c y nunca en uno exacto de 15c.
Edita tu módulo de expendedora para añadir la devolución de cambio: un
LEDR[6]enclavado que se encienda solo cuando la máquina dispensa desde el camino de 20c (S10 + moneda de 10c), junto alLEDR[7]existente.Sintetiza, corrige cualquier typo y carga en la FPGA.
Resetea. Haz un camino exacto de 15c, por ejemplo tres monedas de 5c: confirma que
LEDR[7]se enciende yLEDR[6]queda apagado.Resetea. Haz un camino de 20c, por ejemplo dos monedas de 5c y una de 10c, o dos de 10c: confirma que ambos
LEDR[7]yLEDR[6]se encienden.
Tras tu cambio 3-C1, ejecuta cada camino y lee los dos LED de salida. Registra si LEDR[7] (dispense) y LEDR[6] (cambio) quedan encendidos al final.
| Camino de monedas | Credito alcanzado (15c/20c) | LEDR[7] dispense? (si/no) | LEDR[6] cambio? (si/no) |
|---|---|---|---|
Sube tu evidencia. En un camino de 20c, por ejemplo dos monedas de 5c y una de 10c, captura una imagen de la cámara mostrando ambos LEDR[7] (dispense) y LEDR[6] (cambio) encendidos.
Captura tu código del laboratorio. Guarda tu leds_mirror.v modificado en el laboratorio y usa el control de evidencia de código de abajo para entregar la versión del checkpoint 3-C1 con devolución de cambio. Si tu docente pide un diagrama de estados, entrégalo por separado.
En tu FSM modificada, en exactamente qué caso de estado y entrada se activa la salida de cambio (LEDR[6]), y por qué es el *único* caso de sobrepago de esta máquina?
Checkpoint 3-C2 (opcional / bonus): selección de producto
8 min
Este bonus hace que la máquina venda dos productos: un refresco de 15 céntimos (seleccionado con SW[2]) y un café de 20 céntimos (seleccionado con SW[3]). La máquina debe dispensar solo cuando el crédito alcance el precio del producto seleccionado: con refresco dispensa a 15c, y con café espera hasta 20c.
La idea importante es que esto aumenta el espacio de estados. La máquina ya no se describe solo por crédito, porque su comportamiento con, por ejemplo, 15c depende de *qué producto* se seleccionó: dispensar si es refresco, seguir esperando si es café. La máquina debe recordar el crédito y la selección. Combínalos, por ejemplo con estados como el par *(producto seleccionado, nivel de crédito)*, y decide qué combinaciones pueden ocurrir y qué hace cada una.
Dibuja primero el diagrama de estados ampliado, decide cuántos estados necesitas realmente y luego implementa. No hay una única codificación correcta, pero sí un número mínimo de estados distintos que exige el comportamiento; de ahí sale el mínimo de biestables.
(Bonus) Entrega tu diseño. Adjunta el diagrama de estados ampliado para la máquina de dos productos y pega tu Verilog modificado, o adjunta el código como .txt.
(Bonus) Para tu máquina de dos productos, cuántos estados distintos necesita, y por tanto cuál es el número mínimo de biestables requerido? Recuerda que el mínimo de biestables para codificar N estados es ceil(log2(N)). Indica tu N, muestra el cálculo ceil(log2(N)) y justifica brevemente tu conteo de estados.
Checkpoint 3-C3 (opcional / bonus): display decimal de dos dígitos
7 min
La máquina base muestra el crédito como un solo carácter hexadecimal (0, 5, A, F), compacto pero poco parecido a una máquina real. Este bonus muestra el crédito como dos dígitos decimales en HEX1:HEX0: 00, 05, 10, 15 y, con los caminos de 3-C1/3-C2, 20. HEX0 muestra unidades y HEX1 decenas.
Reutiliza la idea del decodificador de siete segmentos de la Lección 2: una función seg que convierte un dígito decimal 0..9 al patrón activo en bajo {g,f,e,d,c,b,a}. Separa el crédito en decenas y unidades, decodifica cada una y controla HEX1 y HEX0. Recuerda añadir output [6:0] HEX1; a la lista de puertos del módulo: la plantilla inicial solo expone HEX0, así que HEX1 debe declararse, igual que hiciste con el decodificador de dos dígitos en la Lección 2.
Una función decimal seg y una separación para los valores de crédito usados (0, 5, 10, 15, 20) es suficiente:
output [6:0] HEX1; // añade esto a la lista de puertos (decenas)
// ...
function [6:0] seg; // dígito decimal 0..9 -> activo en bajo {g,f,e,d,c,b,a}
input [3:0] d;
case (d)
4'd0: seg = 7'b1000000; 4'd1: seg = 7'b1111001;
4'd2: seg = 7'b0100100; 4'd3: seg = 7'b0110000;
4'd4: seg = 7'b0011001; 4'd5: seg = 7'b0010010;
4'd6: seg = 7'b0000010; 4'd7: seg = 7'b1111000;
4'd8: seg = 7'b0000000; 4'd9: seg = 7'b0010000;
default: seg = 7'b1111111;
endcase
endfunction
wire [4:0] cents = credit_in_cents; // tu valor 0,5,10,15,20 (5 bits)
wire [3:0] tens = (cents >= 5'd20) ? 4'd2 : (cents >= 5'd10) ? 4'd1 : 4'd0;
wire [3:0] ones = cents - (tens * 4'd10);
assign HEX0 = seg(ones);
assign HEX1 = seg(tens);(Bonus) Edita tu módulo: añade
output [6:0] HEX1;a la lista de puertos, añade el decodificador decimalseg, separa el crédito en decenas y unidades y controlaHEX1:HEX0.Sintetiza, corrige cualquier typo y carga en la FPGA.
Resetea e introduce monedas. Confirma que
HEX1:HEX0muestra00,05,10,15y20en un camino con sobrepago.
(Bonus) Sube tu evidencia. Captura una imagen de la cámara de HEX1:HEX0 mostrando un crédito decimal de dos dígitos, por ejemplo 10 o 15, y adjúntala abajo.
(Bonus) Para un crédito de 15 céntimos, qué dígito de decenas y qué dígito de unidades calcula el decodificador (los valores que alimentan HEX1 y HEX0), y qué display muestra 5?
Cierre del capstone: qué construiste en el curso
12 min
Has construido en hardware real un recorrido completo por la lógica digital:
- Puertas (Lección 1): AND, OR, NOT, NAND, NOR, XOR; los átomos de todo circuito y la idea de que Verilog *describe hardware* en vez de ejecutarse como un programa.
- Diseño combinacional y minimización (Lección 2): tablas de verdad, SOP/POS, mapas de Karnaugh y multiplexores; convertir una especificación en el circuito *más simple* que la cumple.
- Memoria (Lección 3): biestable y registro de carga paralela; el momento en que un circuito puede *recordar*.
- Contadores y registros de desplazamiento (Lección 4): bloques secuenciales movidos por un divisor de reloj, más la disciplina de comprobar que una constante cabe en su registro y que el orden de bits coincide con lo que ves.
- Máquinas de estados finitos (Lección 5): Moore y Mealy; un semáforo y un detector de secuencia que actúan según un estado recordado.
- Capstone FSM (esta lección): una máquina expendedora que *diseñaste* desde una tabla de transiciones y luego *ampliaste*: devolución de cambio, selección de producto, display decimal y razonamiento sobre número de estados y biestables.
Ese es el núcleo de un curso introductorio de lógica digital, y ejecutaste cada pieza en una FPGA real.
A dónde seguir. Algunas direcciones opcionales:
- Temporización: setup y hold. Los biestables reales exigen que su entrada sea estable durante una pequeña ventana alrededor del flanco de reloj; cumplir esos tiempos permite alcanzar una frecuencia objetivo. Es la puerta a análisis estático de temporización.
- Aritmética y ALU. Combinar sumadores, comparadores y lógica en una unidad aritmético-lógica, corazón computacional de un procesador.
- Pipelining. Dividir un camino combinacional largo en etapas con reloj para aumentar rendimiento.
- Otros HDL y simulación. SystemVerilog y VHDL amplían lo visto; la *simulación* con testbenches y formas de onda permite verificar un diseño antes de tocar la placa.
Lecturas opcionales. Los cursos gratuitos de FPGA Academy (<https://fpgacademy.org/courses.html>) cubren esta familia DE1-SoC en profundidad, y Harris & Harris, *Digital Design and Computer Architecture*, capítulos 1-3, es un texto excelente para el recorrido de puertas a FSM. No son necesarios para completar la actividad; están aquí si quieres seguir.
En unas frases, recorre cómo creció la idea de *memoria* en este curso: desde un biestable (Lección 3), a un registro y contador (Lecciones 3-4), hasta una FSM cuyo estado tenía significado (Lecciones 5-6). Qué permitió hacer la memoria que las puertas puras no podían?
En tres frases, resume tu capstone: (1) qué representaba el *estado* de tu máquina expendedora y cómo lo cambiaba una moneda, (2) una función que añadiste en un checkpoint y la evidencia que capturaste, y (3) un lugar donde la polaridad activa en bajo o una salida enclavada importó para ver el resultado en la cámara.