All lessons
Intermediate
Project
~45 min
Project: coin-operated vending machine
This is your first proper project. The spec is fixed, the starter code hands you the plumbing, and your job is to write the logic that completes it.
What you're building
An FSM that tracks the money a customer has inserted and vends a product (a single output pulse, dispense) when the total reaches 25¢. The inputs are:
nickel— a 1-cycle pulse meaning "a 5¢ coin just dropped in"dime— a 1-cycle pulse meaning "a 10¢ coin just dropped in"clk,reset
And the outputs:
dispense— pulse high for exactly one clock cycle when you've reached ≥ 25¢change— pulse high for one cycle alongsidedispenseif the total was more than 25¢ (so the machine needs to return money)
Acceptance criteria
| Coin sequence | Expected behavior |
|---|---|
| 5 nickels | dispense=1, change=0 |
| 2 dimes + 1 nickel | dispense=1, change=0 |
| 3 dimes (= 30¢) | dispense=1, change=1 |
| 2 nickels + 1 dime | no dispense (only 20¢) |
What the starter gives you
The entity and total accumulator are wired up. Every nickel pulse adds 5; every dime pulse adds 10. But the starter always drives dispense = '0' and change = '0'. That's what you need to change.
process(clk, reset)
begin
if reset = '1' then
total <= (others => '0');
elsif rising_edge(clk) then
if nickel = '1' then total <= total + 5;
elsif dime = '1' then total <= total + 10;
end if;
end if;
end process;
dispense <= '0'; -- TODO
change <= '0'; -- TODO
Suggested approach
- Detect the sale condition. On every rising clock edge, check if
total >= 25after adding the current coin. (Careful: you need to compute the new total, not the one from the previous cycle.) - Pulse
dispensefor exactly one cycle. The trick: when you detect the sale, set a flag signal and resettotalback to zero. The flag lives for one cycle. - Compute
change. If the total at the sale moment was > 25, pulsechangetoo.
Hint: it's easier if you compute the "next total" as a local signal (or variable inside the process):
variable next_total : unsigned(5 downto 0);
...
next_total := total;
if nickel = '1' then next_total := total + 5;
elsif dime = '1' then next_total := total + 10;
end if;
if next_total >= 25 then
dispense <= '1';
change <= '1' when next_total > 25 else '0';
total <= (others => '0');
else
dispense <= '0';
change <= '0';
total <= next_total;
end if;
Going further
- Add a refund button that zeros out
totalwithout dispensing. - Support a quarter (25¢) coin — should vend immediately.
- Track the number of items sold in a separate 8-bit counter visible as an output. This is the kind of feedback real vending machines send upstream.