Teach Lecciones con laboratorios remotos

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.

  • Altera DE1-SoC
  • 75 min
  • Secundaria (16–18 años)
  • Español
  • Sistemas digitales y FPGA
Altera DE1-SoC
Altera DE1-SoC

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.

1

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).

Diagrama de estados de crédito para una máquina expendedora: S0 0 céntimos, S5 5, S10 10 y S15 15. Una moneda de 5 céntimos SW0 avanza un paso; una moneda de 10 céntimos SW1 avanza dos pasos. Al llegar a 15 céntimos se activa dispense y se vuelve a S0. Desde S10 una moneda de 10 llega a 20 céntimos, dispensa y vuelve a S0. KEY0 resetea cualquier estado a S0.

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?

2

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);
endmodule

Partiendo 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).

3

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.

  1. Abre el laboratorio Verilog de la DE1-SoC. El editor muestra leds_mirror.v.

  2. Selecciona todo el contenido de leds_mirror.v y reemplázalo por el módulo de la expendedora. Mantén el nombre leds_mirror. Observa que declara input CLOCK_50;: la plantilla no añade el reloj.

  3. 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.

  4. Pulsa Enviar a la FPGA y espera a la placa y la cámara.

  5. Pulsa y suelta KEY[0] para resetear. HEX0 debe mostrar 0 y LEDR[7] debe estar apagado.

  6. Tres monedas de 5c. Inserta una moneda de 5c: sube SW[0], espera un tercio de segundo para un tick y vuelve a bajarlo. HEX0 pasa a 5. Repite para llegar a A (10c), y una vez más: el crédito llega a F (15c) y en el siguiente tick se dispensa el producto. LEDR[7] se enciende y HEX0 vuelve a 0. LEDR[7] queda encendido.

  7. Pulsa KEY[0] para resetear (LEDR[7] se apaga y HEX0 muestra 0).

  8. 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.

  9. 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?

4

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] = , volver a S0.
- Dos monedas de 10c (10+10): crédito 20c, LEDR[7] = sí, LEDR[6] = , 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.

  1. 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 al LEDR[7] existente.

  2. Sintetiza, corrige cualquier typo y carga en la FPGA.

  3. Resetea. Haz un camino exacto de 15c, por ejemplo tres monedas de 5c: confirma que LEDR[7] se enciende y LEDR[6] queda apagado.

  4. 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] y LEDR[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?

5

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.

6

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);
  1. (Bonus) Edita tu módulo: añade output [6:0] HEX1; a la lista de puertos, añade el decodificador decimal seg, separa el crédito en decenas y unidades y controla HEX1:HEX0.

  2. Sintetiza, corrige cualquier typo y carga en la FPGA.

  3. Resetea e introduce monedas. Confirma que HEX1:HEX0 muestra 00, 05, 10, 15 y 20 en 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?

7

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.