Teach Remote lab lessons

Teach lesson

Digital Logic on the DE1-SoC (6/6): FSM capstone — a vending machine

Students synthesize and upload a Verilog vending-machine FSM to the DE1-SoC, then test switch inputs, KEY reset, LEDR dispense, and HEX credit output.

  • Altera DE1-SoC
  • 75 min
  • Undergraduate, introductory
  • English
  • Digital systems & FPGA
Altera DE1-SoC
Altera DE1-SoC

Learning Outcomes

  • Design and extend a non-trivial finite state machine on real hardware.

  • Build a vending-machine controller whose state is the accumulated credit.

  • Add features in scaffolded checkpoints: change return, item selection, and a decimal display.

  • Reason about how many states a design needs and how many flip-flops that requires.

Student activity preview

Activity Content

Preview only. In a class session, students can fill in responses and submit their work to the teacher.

1

From detector to controller: an FSM that holds your money

8 min

In Lesson 5 you built finite state machines whose state was an abstract position in a recognizer: a traffic light cycling green → yellow → red, and a "1011" sequence detector remembering how much of the pattern it had seen so far. In this capstone the state means something concrete and physical: how much money you have put in so far. You are going to build a small vending-machine controller and then grow it, feature by feature.

A finite state machine, recall, has three parts: a set of states, a rule for the next state given the current state and the inputs, and an output that depends on the state (and, for a Mealy machine, the inputs too). The skill this lesson adds is design: you decide what the states should be, draw the transition table, and only then write the Verilog.

For a coin machine the natural choice is to let each state *be* a credit level. If the machine accepts nickels (5 cents) and dimes (10 cents) and dispenses at 15 cents, then the credit can be 0, 5, 10, or 15 cents — four states. We will call them S0, S5, S10, S15. Inserting a coin moves you to a higher-credit state; reaching the price dispenses the item and returns you to S0.

The board signals are the same as always. Because this is a clocked (sequential) design, the module declares the clock itself — the default leds_mirror.v template has no clock, so we add input CLOCK_50;. As in Lesson 5, a switch up is logic 1, KEY is active-low (a pressed button reads 0), LEDR is active-high (lit when driven 1), and HEX0HEX5 are active-low seven-segment displays (a segment lights when driven 0).

A credit-state diagram for the vending machine. Four states S0 (0 cents), S5 (5 cents), S10 (10 cents), and S15 (15 cents) are drawn left to right. A nickel transition (SW0) advances credit by one step (S0 to S5, S5 to S10, S10 to S15); a dime transition (SW1) advances by two steps (S0 to S10, S5 to S15). Reaching 15 cents asserts the dispense output and returns to S0; from S10 a dime reaches 20 cents, which also dispenses and returns to S0. A reset arrow (KEY0) points every state back to S0.

The vending machine as a credit-state diagram. Each state is a credit level. A nickel (SW[0]) advances one step; a dime (SW[1]) advances two steps. Reaching the price asserts the dispense output and returns to S0; KEY[0] resets any state to S0. Later checkpoints add a second output (change return) and more states (item selection).

The base machine uses four separate states S0, S5, S10, S15. Why does it need a different state for each credit level instead of a single state — what could the machine *not* do if it only had one state?

LEDR[7] (dispense) is latched and stays lit until you press KEY[0]. After the item dispenses and the machine returns to S0, what clears the latch — and what problem would arise if nothing ever cleared it?

2

Exercise 3-C: the vending-machine FSM

14 min

Here is the complete base design. Read it as a finite state machine in the now-familiar three blocks: a clock divider (so coin insertions happen at a human pace), a combinational next-state/output block, and a clocked state register with an asynchronous active-low reset.

The states. S0, S5, S10, S15 are the four credit levels, encoded as 2-bit values 0..3. The current credit in cents is derived from the state and shown two ways: on LEDR[3:0] as a binary number of cents, and on HEX0 as a single character — 0 for 0c, 5 for 5c, A for 10c, and F for 15c (these are just the hex digits whose values are 0, 5, 10, and 15).

The transitions (this is the table you would draw first when designing):

Transition summary:

- S0 (0c): nickel -> S5; dime -> S10; no dispense.
- S5 (5c): nickel -> S10; dime -> S15; no dispense.
- S10 (10c): nickel -> S15; dime -> S0 and dispense at 20c with no change in the base version.
- S15 (15c): on the next tick -> S0 and dispense, regardless of input.

Two details are worth pausing on. From S10, inserting a dime reaches 20 cents — more than the 15-cent price — so the machine dispenses and returns to S0, but in this base version it gives no change (the checkpoint below fixes that). And S15 is a "dispense and reset" state: on the very next tick it asserts dispense and returns to S0, regardless of input. So when you feed coins that land exactly on 15c, you will see the credit reach F, and then on the following tick the item dispenses.

The dispense output is latched. dispense itself is a one-tick signal. As in Lesson 5's detector, a single-tick pulse is easy to miss on a remote camera, so we latch it into dispensed_latch, drive it to LEDR[7], and only clear it on reset. That way, once the machine has dispensed, LEDR[7] stays lit until you press KEY[0].

Inserting a coin — the timing. The divider here uses DIV = 16,666,666, so slow_clk toggles about every 0.33 s. To insert one coin you toggle a switch up, wait one slow-clock tick (about a third of a second), and set it back to 0. Holding a switch up across several ticks would count as several coins. Insert coins one at a time.

// Lesson 6 - Exercise 3-C: vending machine FSM (capstone base design)
// SW[0]=nickel (5c), SW[1]=dime (10c), KEY[0]=reset (active-low).
// LEDR[7]=dispense (LATCHED until reset). LEDR[3:0]=credit in cents. HEX0=credit (0,5,A=10,F=15).
// Insert a coin: toggle a switch up, wait one slow-clock tick (~0.33 s), set it back to 0.
// Dispenses when credit reaches 15c (or 20c from S10+dime); no change in this base version.
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]=nickel, SW[1]=dime
    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;  // credit levels
    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, no change
            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

Starting from S0, predict — before you run it — the sequence of characters you will see on HEX0 and when LEDR[7] (dispense) lights if you insert a dime, then a nickel (one per tick).

3

Run the vending machine: coins, dispense, reset

14 min

Build the controller on the real board, feed it coins along two different paths, and confirm the dispense LED latches.

  1. Open the DE1-SoC Verilog lab. The editor shows a file leds_mirror.v.

  2. Select all of the contents of leds_mirror.v and replace them with the vending-machine module above. Keep the module named leds_mirror. Note that it declares input CLOCK_50; — the template does not add the clock for you.

  3. Click Synthesize. If a guided tour covers the button, close or skip the tour first. Wait for it to finish (about 1-3 minutes). Fix any reported typo and Synthesize again. Continue only when the final status says the build succeeded with 0 errors.

  4. Click Upload to FPGA and wait for the live board session and camera.

  5. Press and release KEY[0] to reset. HEX0 should show 0 and LEDR[7] should be off.

  6. Three nickels. Insert a nickel: set SW[0] up, wait about a third of a second for one tick, then set SW[0] back down. Watch HEX0 go to 5. Repeat to reach A (10c), and once more — credit reaches F (15c), then on the next tick the item dispenses: LEDR[7] lights and HEX0 returns to 0. LEDR[7] stays lit.

  7. Press KEY[0] to reset (LEDR[7] clears, HEX0 shows 0).

  8. Nickel + nickel + dime. Insert two nickels (HEX0: 05A), then insert a dime (SW[1]): from S10, a dime reaches 20c, so the machine dispenses immediately and returns to S0. LEDR[7] lights.

  9. Press KEY[0] to reset before the next checkpoint.

Reset first. Insert coins one per tick and record, after each coin, the character on HEX0 (the credit) and whether LEDR[7] (dispense) is now lit. Do the three-nickel path first, then reset and do the nickel+nickel+dime path.

Coin inserted HEX0 credit (0/5/A/F) LEDR[7] dispense? (yes/no)

Upload your evidence. After three nickels make the machine dispense, capture a camera screenshot with LEDR[7] lit, and attach it below.

When the credit reaches 15c (HEX0 shows F), the item does not dispense on that same tick — it dispenses on the next tick. Looking at the S15 case in the code, explain in one sentence why.

In the base design, inserting a dime from S10 (10c) reaches 20c and dispenses, but the customer loses 5 cents. Which output would you need to add to fix this, and in which single case does it apply?

4

Checkpoint 3-C1 (required): add change return

12 min

Your first extension fixes the unfair case you just identified. When the machine reaches 20 cents — the only over-payment path, S10 followed by a dime — it should light a second LED, LEDR[6], meaning "return 5c change," in addition to dispensing on LEDR[7]. Every other dispense (exactly 15c) returns no change.

This is a small, surgical change to the FSM. You need a second latched output, change_latch, driven to LEDR[6], set whenever the machine dispenses *from the 20-cent path*. The cleanest way is to add a combinational change signal alongside dispense, assert it only in the S10 + dime branch, and latch it exactly like dispensed_latch.

This is the expected behavior after your change:

Expected checkpoint behavior:

- Three nickels (5+5+5): credit reaches 15c, LEDR[7] dispense = yes, LEDR[6] change = no, return to S0.
- Nickel + dime (5+10): credit reaches 15c, LEDR[7] dispense = yes, LEDR[6] change = no, return to S0.
- Dime + nickel (10+5): credit reaches 15c, LEDR[7] dispense = yes, LEDR[6] change = no, return to S0.
- Nickel + nickel + dime (5+5+10): credit reaches 20c, LEDR[7] dispense = yes, LEDR[6] change = yes, return to S0.
- Two dimes (10+10): credit reaches 20c, LEDR[7] dispense = yes, LEDR[6] change = yes, return to S0.

Implement it, then verify on the board that LEDR[6] lights only on a 20-cent path and never on an exact-15-cent path.

  1. Edit your vending module to add the change-return output: a latched LEDR[6] that lights only when the machine dispenses from the 20-cent (S10 + dime) path, alongside the existing LEDR[7] dispense.

  2. Synthesize, fix any typo, and Upload to FPGA.

  3. Reset. Do an exact-15c path (e.g. three nickels): confirm LEDR[7] lights and LEDR[6] stays off.

  4. Reset. Do a 20c path (two nickels then a dime, or two dimes): confirm both LEDR[7] and LEDR[6] light.

After your 3-C1 change, run each path, then read the two output LEDs. Record whether LEDR[7] (dispense) and LEDR[6] (change) are lit at the end of each path.

Coin path Credit reached (15c/20c) LEDR[7] dispense? (yes/no) LEDR[6] change? (yes/no)

Upload your evidence. On a 20-cent path (e.g. two nickels then a dime), capture a camera screenshot showing both LEDR[7] (dispense) and LEDR[6] (change) lit, and attach it below.

Capture your lab code. Save your modified leds_mirror.v in the lab, then use the code evidence control below to submit the checkpoint 3-C1 version with change return. If your teacher asks for a state diagram, submit that separately.

In your modified FSM, in exactly which state-and-input case does the change output (LEDR[6]) become asserted, and why is that the *only* over-payment case in this machine?

5

Checkpoint 3-C2 (optional / bonus): item selection

8 min

This bonus checkpoint makes the machine sell two products: a soda at 15 cents (selected with SW[2]) and a coffee at 20 cents (selected with SW[3]). The machine should dispense only when the credit reaches the price of the selected item — so with soda selected it dispenses at 15c, and with coffee selected it waits until 20c.

The important idea here is that this enlarges the state space. The machine can no longer be described by credit alone, because its behavior at, say, 15 cents now depends on *which item* was selected: dispense if soda, keep waiting if coffee. The machine must remember both the credit and the selection. Combine them — for example a state is the pair *(selected item, credit level)* — and work out which combinations can actually occur and what each one does.

Draw the expanded state diagram first, decide how many states you genuinely need, then implement it. There is no single "right" encoding, but there is a minimum number of distinct states your behavior requires — and from that, a minimum number of flip-flops.

(Bonus) Submit your design. Attach your expanded state diagram for the two-product machine and paste your modified Verilog, or attach the code as a .txt file.

(Bonus) For your two-product machine, how many distinct states does it need, and therefore what is the minimum number of flip-flops required? Recall that the minimum number of flip-flops to encode N states is ceil(log2(N)). State your N, show the ceil(log2(N)) calculation, and briefly justify your state count.

6

Checkpoint 3-C3 (optional / bonus): a two-digit decimal display

7 min

The base machine shows credit as a single hex character (0, 5, A, F), which is compact but not how a real machine displays money. This bonus checkpoint shows the credit as a two-digit decimal on HEX1:HEX000, 05, 10, 15, and (with the 3-C1 / 3-C2 paths) 20. HEX0 shows the ones digit, HEX1 shows the tens digit.

This reuses the seven-segment decoder idea from Lesson 2: a seg function that maps a single decimal digit 0..9 to the active-low segment pattern {g,f,e,d,c,b,a}. Split the credit into a tens digit and a ones digit, decode each, and drive HEX1 and HEX0. Remember to add output [6:0] HEX1; to the module's port list — the default template exposes only HEX0, so HEX1 must be declared, exactly as you did for the two-digit decoder in Lesson 2.

A decimal seg function (the same shape as Lesson 2's) and a split for the credit values you actually use (0, 5, 10, 15, 20) is all you need:

    output [6:0] HEX1;                 // add this to the port list (tens digit)
    // ...
    function [6:0] seg;                // decimal digit 0..9 -> active-low {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;          // your 0,5,10,15,20 value (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) Edit your vending module: add output [6:0] HEX1; to the port list, add a decimal seg decoder, split the credit into tens and ones, and drive HEX1:HEX0.

  2. Synthesize, fix any typo, and Upload to FPGA.

  3. Reset and feed coins. Confirm HEX1:HEX0 reads 00, 05, 10, 15 (and 20 on an over-payment path) as the credit climbs.

(Bonus) Upload your evidence. Capture a camera screenshot of HEX1:HEX0 showing a two-digit decimal credit (for example 10 or 15), and attach it below.

(Bonus) For a credit of 15 cents, what tens digit and ones digit does the decoder compute (the values feeding HEX1 and HEX0), and which display shows 5?

7

Capstone wrap-up: what you built across the course

12 min

You have now built, on real hardware, a complete path through digital logic:

- Gates (Lesson 1): AND, OR, NOT, NAND, NOR, XOR — the atoms of every circuit, and the idea that Verilog *describes hardware* rather than running like a program.
- Combinational design and minimization (Lesson 2): truth tables, sum-of-products and product-of-sums, Karnaugh maps, and a multiplexer — turning a specification into the *simplest* circuit that meets it.
- Memory (Lesson 3): the flip-flop and the parallel-load register — the moment a circuit gains the ability to *remember*.
- Counters and shift registers (Lesson 4): sequential building blocks driven through a clock divider, plus the discipline of checking that a constant fits its register and that bit order matches what you see.
- Finite state machines (Lesson 5): Moore and Mealy machines — a traffic light and a sequence detector — that act on a remembered state.
- An FSM capstone (this lesson): a vending-machine controller you *designed* from a transition table and then *extended* — change return, item selection, a decimal display — reasoning about state count and flip-flop count along the way.

That is the core of an introductory digital-logic course, and you ran every piece of it on a real FPGA.

Where this goes next. A few directions, all optional, if you want to keep going:

- Timing — setup and hold. Real flip-flops require their input to be stable for a short window around the clock edge; respecting these *setup* and *hold* times is what lets a design run at a target clock frequency. This is the gateway to static timing analysis.
- Arithmetic and ALUs. Combining adders, comparators, and logic into an arithmetic-logic unit — the computational heart of a processor.
- Pipelining. Splitting a long combinational path into clocked stages to raise throughput, a central technique in fast hardware.
- Other HDLs and simulation. SystemVerilog and VHDL extend what you have seen; and *simulation* (with testbenches and waveforms) lets you verify a design before it ever touches a board.

Optional further reading. The free FPGA Academy courses (<https://fpgacademy.org/courses.html>) cover this same DE1-SoC family in depth, and Harris & Harris, *Digital Design and Computer Architecture*, chapters 1-3, is an excellent text for the gates-to-FSMs material you have just worked through. Neither is required — they are here if you want to go further.

In a few sentences, trace how the *memory* idea grew across this course: from a single flip-flop (Lesson 3), to a register and counter (Lessons 3-4), to a finite state machine whose state carried meaning (Lessons 5-6). What did adding memory let your circuits do that pure gates could not?

In three sentences, summarize your capstone: (1) what the *state* of your vending machine represented and how a coin changed it, (2) one feature you added in a checkpoint and the evidence you captured for it, and (3) one place where active-low or latched-output behavior mattered for seeing the result on the camera.