Teach lesson
Digital Logic on the DE1-SoC with VHDL (6/6): FSM capstone -- a vending machine
Students build a VHDL vending-machine FSM, test credit paths on hardware, and try optional change-return checkpoints if time permits.
New to LabsLand? Create your teacher account
Learning Outcomes
Run and explain a non-trivial finite state machine on real hardware.
Build a vending-machine controller whose state is the accumulated credit.
Recognize change return, item selection, and decimal display as optional scaffolded checkpoints.
Explain why the base design needs remembered credit states; if attempting 3-C2, reason about state count and flip-flops.
Student activity preview
Activity Content
Preview only. In a class session, students can fill in responses and submit their work to the teacher.
From traffic light to controller: an FSM that holds your money
8 min
In Lesson 5 you built a finite state machine whose state was an abstract position in a cycle: a traffic light cycling green -> yellow -> red. If you did the optional detector, you also saw a state remember part of a pattern. 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; optional checkpoints can 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 VHDL.
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 entity declares the clock itself — the default blink.vhd template has no clock, so we add CLOCK_50 : in std_logic. 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 HEX0–HEX5 are active-low seven-segment displays (a segment lights when driven 0).
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. Exact-price paths enter S15 first; on the next tick S15 asserts dispense and returns to S0. Optional 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?
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 named credit levels. The current credit in cents is derived explicitly from state comparisons and shown two ways: on LEDR(3 downto 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, but the FSM samples coins only on rising edges. In practice, treat one coin tick as about 0.67 s. To insert one coin, toggle a switch up, wait at least one rising-edge tick, and set it back to 0. Holding a switch up across several ticks would count as several coins. Insert coins one at a time.
-- Student starter: fill the TODO lines before synthesizing.
-- Lesson 6 - Exercise 3-C: vending machine FSM capstone base design.
-- SW(0)=nickel, SW(1)=dime, KEY(0)=reset. LEDR(7)=dispense latch.
-- Insert one coin by holding SW(0) or SW(1) high for one slow_clk rising-edge tick, about 0.67 s.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity blink is
port (
CLOCK_50 : in std_logic;
KEY : in std_logic_vector(3 downto 0);
SW : in std_logic_vector(9 downto 0);
LEDR : out std_logic_vector(9 downto 0);
HEX0 : out std_logic_vector(6 downto 0)
);
end entity blink;
architecture rtl of blink is
type state_t is (S0, S5, S10, S15);
signal state, next_state : state_t := S0;
signal dispense, dispensed_latch : std_logic := '0';
signal credit : unsigned(3 downto 0);
constant DIV : unsigned(24 downto 0) := to_unsigned(16666666, 25);
signal div_count : unsigned(24 downto 0) := (others => '0');
signal slow_clk : std_logic := '0';
function seg(d : unsigned(3 downto 0)) return std_logic_vector is
begin
case d is
when "0000" => return "1000000"; -- 0
when "0101" => return "0010010"; -- 5
when "1010" => return "0001000"; -- A = 10
when "1111" => return "0001110"; -- F = 15
when others => return "1111111";
end case;
end function;
begin
process(CLOCK_50)
begin
if rising_edge(CLOCK_50) then
if KEY(0) = '0' then
div_count <= (others => '0'); slow_clk <= '0';
elsif div_count = DIV then
div_count <= (others => '0'); slow_clk <= not slow_clk;
else
div_count <= div_count + 1;
end if;
end if;
end process;
process(state, SW)
begin
next_state <= state;
dispense <= '0';
case state is
when S0 => TODO_FROM_0_CENTS;
when S5 => TODO_FROM_5_CENTS;
when S10 => TODO_FROM_10_CENTS;
when S15 => TODO_DISPENSE_AND_RESET;
end case;
end process;
process(slow_clk, KEY)
begin
if KEY(0) = '0' then
state <= S0; dispensed_latch <= '0';
elsif rising_edge(slow_clk) then
state <= next_state;
if TODO_DISPENSE_CONDITION then dispensed_latch <= '1'; end if;
end if;
end process;
credit <= to_unsigned(0, 4) when state = S0 else
to_unsigned(5, 4) when state = S5 else
to_unsigned(10, 4) when state = S10 else
to_unsigned(15, 4);
LEDR(7) <= TODO_DISPENSE_LED;
LEDR(3 downto 0) <= std_logic_vector(credit);
LEDR(6) <= '0'; -- replace this line with change_latch in the 3-C1 extension
LEDR(5 downto 4) <= (others => '0');
LEDR(9 downto 8) <= (others => '0');
HEX0 <= seg(credit);
end architecture rtl;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).
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.
Open the DE1-SoC VHDL lab. The editor shows a file
blink.vhd.Select all of the contents of
blink.vhdand replace them with the vending-machine starter above. Fill every TODO transition, condition, and output, keep the entity namedblink, and note that it declaresCLOCK_50 : in std_logic— the template does not add the clock for you.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.
Click Upload to FPGA and wait for the live board session and camera.
Press and release
KEY(0)to reset.HEX0should show0andLEDR(7)should be off.Three nickels. Insert a nickel: set
SW(0)up, wait about 0.67 s for one coin tick, then setSW(0)back down. WatchHEX0go to5. Repeat to reachA(10c), and once more — credit reachesF(15c), then on the next tick the item dispenses:LEDR(7)lights andHEX0returns to0.LEDR(7)stays lit.Press
KEY(0)to reset (LEDR(7)clears,HEX0shows0).Nickel + nickel + dime. Insert two nickels (
HEX0:0→5→A), then insert a dime (SW(1)): from S10, a dime reaches 20c, so the machine dispenses immediately and returns to S0.LEDR(7)lights.Press
KEY(0)to reset before the next checkpoint.
Reset first. Insert coins one per tick and record HEX0 and LEDR(7). For exact-15c paths, record the row when the credit first reaches F with dispense still off, then wait one extra tick and record the dispense row. 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?
Optional checkpoint 3-C1: add change return
12 min
Optional checkpoint if your teacher wants an extension: this 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, latch it exactly like dispensed_latch, and replace the base line LEDR(6) <= '0'; with LEDR(6) <= change_latch;.
This is the expected behavior after your change:
Expected checkpoint behavior:
- Three nickels (5+5+5): credit reaches 15c; wait one extra coin tick, then LEDR(7) dispense = yes, LEDR(6) change = no, return to S0.
- Nickel + dime (5+10): credit reaches 15c; wait one extra coin tick, then LEDR(7) dispense = yes, LEDR(6) change = no, return to S0.
- Dime + nickel (10+5): credit reaches 15c; wait one extra coin tick, then 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.
Edit your vending entity to add the change-return output: declare and latch
change_latch, then replace the base lineLEDR(6) <= '0';withLEDR(6) <= change_latch;. It should light only when the machine dispenses from the 20-cent (S10 + dime) path, alongside the existingLEDR(7)dispense.Synthesize, fix any typo, and Upload to FPGA.
Reset. Do an exact-15c path (e.g. three nickels), wait the extra dispense tick, then confirm
LEDR(7)lights andLEDR(6)stays off.Reset. Do a 20c path (two nickels then a dime, or two dimes): confirm both
LEDR(7)andLEDR(6)light.
After your 3-C1 change, run each path, then read the two output LEDs. For exact-15c paths, wait the extra dispense tick before reading. 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 blink.vhd 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?
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 VHDL, 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.
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:HEX0 — 00, 05, 10, and 15. Only show 20 if you build a separate extension with an explicit 20c display state or display latch; the 3-C1 change-return design still returns the credit display to 00 while LEDR(6) indicates change. HEX0 shows the ones digit, HEX1 shows the tens digit.
This reuses the seven-segment decoder idea from Lesson 2: a decoder helper, named seg_dec here so it can coexist with the base 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 declare HEX1 : out std_logic_vector(6 downto 0) in the entity's port list — the base entity example in this lesson exposes only HEX0, so HEX1 must be declared, exactly as you did for the two-digit decoder in Lesson 2. Also replace the original HEX0 <= seg(credit); line; do not leave both HEX0 assignments active.
A decimal seg_dec helper (the same shape as Lesson 2's seg function) and a split for the base credit values (0, 5, 10, 15) is all you need:
-- Add HEX1 to the entity port list, then complete the display helper.
HEX0 : out std_logic_vector(6 downto 0);
HEX1 : out std_logic_vector(6 downto 0)
-- ...
function seg_dec(d : unsigned(3 downto 0)) return std_logic_vector is
begin
case d is
when "0000" => return "1000000";
when "0001" => return TODO_PATTERN_FOR_1;
when "0010" => return TODO_PATTERN_FOR_2;
when others => return "1111111";
end case;
end function;
signal cents : unsigned(4 downto 0);
signal tens : unsigned(3 downto 0);
signal ones : unsigned(3 downto 0);
-- ...
cents <= TODO_CREDIT_AS_CENTS;
tens <= TODO_TENS_DIGIT;
ones <= TODO_ONES_DIGIT;
HEX0 <= TODO_ONES_DISPLAY;
HEX1 <= TODO_TENS_DISPLAY;(Bonus) Edit your vending entity: add a
HEX1 : out std_logic_vector(6 downto 0)port to the entity port list, add the decimalseg_decdecoder, split the credit into tens and ones, replace the originalHEX0 <= seg(credit);line with the newHEX0/HEX1display drivers, and driveHEX1:HEX0.Synthesize, fix any typo, and Upload to FPGA.
Reset and feed coins. Confirm
HEX1:HEX0reads00,05,10,15as the credit climbs. Only adapt the pattern for20if you build a separate extension with an explicit 20c display state or display latch; the 3-C1 change-return design still returns the credit display to00whileLEDR(6)indicates change.
(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?
Capstone wrap-up: what you built across the course
8 min
You have now built, on real hardware, the core path through digital logic:
- Gates (Lesson 1): AND, OR, NOT, NAND, NOR, XOR — the atoms of every circuit, and the idea that VHDL *describes hardware* rather than running like a program.
- Combinational design and minimization (Lesson 2): truth tables, canonical SOP, Karnaugh maps, and optional POS/MUX/decoder extensions — turning a specification into a simpler 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 (Lesson 4): a sequential building block driven through a clock divider, plus the discipline of checking that a constant fits its register; optional extensions introduced BCD and shift-register behavior.
- Finite state machines (Lesson 5): a Moore traffic light acting on a remembered state, with an optional Mealy sequence-detector extension.
- An FSM capstone (this lesson): a vending-machine controller you *ran and explained* from a transition table; optional checkpoints let you add change return, item selection, or a decimal display and, if attempted, reason about state count and flip-flop count.
That is the core of an introductory digital-logic course, and you ran the required path 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. Advanced VHDL topics and *simulation* with testbenches and waveforms let 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) the evidence you captured for the base dispense path or an optional checkpoint, and (3) one place where active-low or latched-output behavior mattered for seeing the result on the camera.