Breadboard exercises/ M11EnglishРусский

Exercise M11 · mini-project · finite state machine

Traffic light from a decade counter

Red for four seconds, yellow for two, green for four, and again. Timofei Nikiforov, a school student, built this traffic light from a 4017 decade counter and OR gates. It is a finite state machine with ten states in a ring, and the counter chip is its state register.

3 chips4017 counter, 4072 and 4071 OR gates
24 + 10 wireson the board + jumper leads to the modules
10 states4 red, 2 yellow, 4 green
~1 Hz clockfrom the 555 module, no clock circuit to build

1What it is and why it matters

What it does. A traffic light that changes once per clock period: four periods red, two yellow, four green, then red again. With the clock module set to 1 Hz that is 4 seconds of red, 2 of yellow and 4 of green. Timofei Nikiforov, a school student, worked this circuit out on his own and built it with his younger brother Nil (Habr article, lesson 1.7, and “Teaching children electronics is due for a major redesign”, picture 15).

How it works. The 4017 has ten outputs, Q0 to Q9, and exactly one of them is high at any time: Q0 after a reset, Q1 after the first rising clock edge, and so on up to Q9, then back to Q0. Each output is one state of a state machine. Three OR gates turn the states into lights:

red    = Q0 OR Q1 OR Q2 OR Q3
yellow = Q4 OR Q5
green  = Q6 OR Q7 OR Q8 OR Q9

A classic circuit, built with gates. A 4017 clocked by a 555 timer is a well-known hobby traffic light. The usual version joins the 4017 outputs with diodes instead of OR gates and often takes red from CARRY OUT, which is high for counts 0–4 (see related designs in section 15). With OR gates the same idea reads as logic: each light is an OR of states, which is how the circuit is written in SystemVerilog later on this page.

Where it is used. A machine whose states follow each other in a fixed ring, one step per clock, is the simplest kind of finite state machine (FSM). Chips are full of them: a controller that steps through the phases of a transfer (send the address, wait, take the data), the sequencer of a simple processor (fetch, decode, execute, write back), a display driver that lights one digit after another, a round-robin scheduler that gives every requester its turn, a timer that cuts a period into slots. In FPGAs such sequencers are often built exactly like the outputs of the 4017: one flip-flop per state, one of them set at a time (a one-hot code), and outputs that are ORs of states.

Why learn it.

2The key element: a counter with decoded outputs

S0S1S2S3S4S5S6S7S8S9rstevery clock edge:one step clockwise

Every rising edge of the clock moves the machine one step clockwise; a reset puts it into S0 at once. The colour of each circle is the light it shows. The state changes only on the clock edge, and the light depends only on the state: this is a Moore machine.

The ring needs no next-state logic of its own: the counter is the next-state logic. The only design work is the output logic, three OR gates.

Inside, the 4017 is not a binary counter. A binary counter needs four flip-flops for ten states, but its decoder sees several bits change at once (7 → 8 changes all four), and for a moment it may show a wrong state, a glitch.

Inside the 4017: a Johnson counter and a decoder

The 4017 uses five D flip-flops connected as a shift register, with the last bit fed back inverted into the first. Starting from 00000 the register fills with ones from the right and then with zeros, so it goes through ten patterns and changes exactly one bit per clock. Two neighbouring bits identify each pattern, so one 2-input AND gate per state is enough, and the decoder cannot glitch.

StateJohnson register r4…r0Decoder: q = 1 whenQ9…Q0Carry outLight
S000000~r0 & ~r400000000011red
S100001r0 & ~r100000000101red
S200011r1 & ~r200000001001red
S300111r2 & ~r300000010001red
S401111r3 & ~r400000100001yellow
S511111r0 & r400001000000yellow
S611110~r0 & r100010000000green
S711100~r1 & r200100000000green
S811000~r2 & r301000000000green
S910000~r3 & r410000000000green

The symbols

D flip-flop with reset

DQRDclkQrst
rstclkDQ after
1anyany0, at once
0↑00
0↑11
0no edgeanykeeps Q

Q takes the value of D at the rising edge of the clock (↑) and keeps it until the next edge. R = 1 clears Q at once, without the clock.

NOT

AY
AY
01
10

Feeds the inverted last bit back into the first flip-flop. A small circle on a gate input or output means NOT.

AND with an inverted input

ABY
ABY = A AND NOT B
000
010
101
110

One such gate per state in the decoder, for example q1 = r0 AND NOT r1.

OR, 2 and 4 inputs

ABYABCDY
ABY = A OR B
000
011
101
111

Y = 1 when any input is 1. A 4-input OR is 1 when at least one of its four inputs is 1.

4017 decade counter

4017CLKINHRSTQ0Q1Q2Q3Q4Q5Q6Q7Q8Q9CO

CLK counts on the rising edge, INH = 1 holds the count, RST = 1 sets the count to 0. One of Q0–Q9 is high; CO is high in states 0–4.

Waveforms

clkrstr4…r000000000010001100111011111111111110111001100010000000000000100011001110111111111stateS0S1S2S3S4S5S6S7S8S9S0S1S2S3S4S5Q0Q1Q2Q3Q4Q5Q6Q7Q8Q9redyellowgreen
From the simulation of tb.sv in Icarus Verilog. Reset is released between two clock edges; from then on every rising edge (dotted lines) shifts the Johnson register r and moves the state one step. Each output Q0–Q9 is high for exactly one clock period; red lasts four periods, yellow two, green four. · SVG · PNG

3Parts

PartIn this circuitQtyPriceSupplier
CD4017decade counter: the state register and the decoder1$0.40Elexp (elexp.com)
CD4072dual 4-input OR: red and green1$0.25Elexp (elexp.com)
CD4071quad 2-input OR: yellow (one gate used)1$0.25Elexp (elexp.com)
555 clock modulethe clock, set to about 1 Hz1$0.53aliexpress.us
Button modulereset; one module from the pack of five1$0.66 / packaliexpress.us
Traffic-light LED module, 5 Vthe three lamps, resistors built in1$0.40aliexpress.us
Breadboard, 830 points1$3.00amazon.com
Breadboard power module5 V on the rails1$1.00aliexpress.us
USB-to-DC cable, 9 Vpowers the module from a USB charger1$1.17aliexpress.us
Pre-cut jumper wires24 wires on the board24$11.95 / packjameco.com
Male-female Dupont jumpers10 leads to the three modules10$1.30 / packaliexpress.us

Prices from the LSSK components sheet; this exercise is part of the Comprehensive kit. The reset button is optional: without it, connect pin 15 of the 4017 to GND. The 4017 has extra gates that bring it into the ten-state ring from any state after power-up, so the light starts at a random colour and then runs normally.

Buy the 5 V traffic-light module. The same module is also sold in a 1 V version, and the two look alike; lesson 1.7 of the Habr article warns about this. The 5 V version is driven straight from the chip outputs, as here.

4The chips, pin by pin

All three chips are CMOS 4000B series: they work from 3 to 18 V, here from the 5 V of the power module. Every input must be connected to something, because a CMOS input left open picks up noise and can switch at random; that is why the unused inputs of the 4071 are wired to +5V or GND. All outputs here are active high: 1 means +5 V and a lit lamp. The drawings show each chip from above, the way it sits on the breadboard: notch on the left, pin 1 at the bottom left, pins numbered counter-clockwise. Blue arrows are inputs, orange arrows are outputs.

4017: decade counter with 10 decoded outputs

1Q52Q13Q04Q25Q66Q77Q38VSS9Q810Q411Q912CO13INH14CLK15RST16VDD4017decade counter
CD4017B. The triangle at CLOCK marks an edge-triggered input. · SVG · PNG

Polarity. The count advances on the rising edge of CLOCK while CLOCK INHIBIT is low. RESET is active high and asynchronous: while it is 1 the count is 0 and Q0 is high, whatever the clock does. CLOCK INHIBIT is active high: 1 holds the count.

The 4017 also counts on a falling edge of CLOCK INHIBIT while CLOCK is high: CLOCK INHIBIT gates the clock. Here it is tied to GND, so only CLOCK matters.

PinNameDirectionFunctionIn this circuit
1Q5output1 in state 5, 0 otherwiseQ5
2Q1output1 in state 1, 0 otherwiseQ1
3Q0output1 in state 0, 0 otherwiseQ0
4Q2output1 in state 2, 0 otherwiseQ2
5Q6output1 in state 6, 0 otherwiseQ6
6Q7output1 in state 7, 0 otherwiseQ7
7Q3output1 in state 3, 0 otherwiseQ3
8VSSpowerground, 0 VGND
9Q8output1 in state 8, 0 otherwiseQ8
10Q4output1 in state 4, 0 otherwiseQ4
11Q9output1 in state 9, 0 otherwiseQ9
12CARRY OUToutput1 in states 0-4, 0 in states 5-9; rises when the count goes from 9 to 0, so it can clock the next 4017unused
13CLOCK INHIBITinput1 holds the count; 0 lets the counter count. Active hightied to GND
14CLOCKinputthe count advances on the rising edge, 0 to 1CLK
15RESETinput1 sets the count to 0 at once (Q0 = 1), without waiting for the clock. Active highRST
16VDDpowersupply, +5 V (3 to 18 V allowed)+5V

4072: dual 4-input OR

1J2A3B4C5D6NC7VSS8NC9E10F11G12H13K14VDD4072
CD4072B. Pins 6 and 8 are not connected inside. · SVG · PNG

Two independent gates: pin 1 = pins 2 OR 3 OR 4 OR 5, pin 13 = pins 9 OR 10 OR 11 OR 12. Here the first gate makes red and the second green, so all eight inputs are used.

PinNameDirectionFunctionIn this circuit
1JoutputJ = A OR B OR C OR DRED
2Ainputinput of the OR gate that drives pin 1Q3
3Binputinput of the OR gate that drives pin 1Q1
4Cinputinput of the OR gate that drives pin 1Q0
5Dinputinput of the OR gate that drives pin 1Q2
6NC—not connected inside the chipnot connected
7VSSpowerground, 0 VGND
8NC—not connected inside the chipnot connected
9Einputinput of the OR gate that drives pin 13Q7
10Finputinput of the OR gate that drives pin 13Q6
11Ginputinput of the OR gate that drives pin 13Q8
12Hinputinput of the OR gate that drives pin 13Q9
13KoutputK = E OR F OR G OR HGREEN
14VDDpowersupply, +5 V (3 to 18 V allowed)+5V

4071: quad 2-input OR

1A2B3J4K5C6D7VSS8E9F10L11M12G13H14VDD4071
CD4071B. Only the gate with output pin 4 is used; the faded gates have their inputs tied to the rails. · SVG · PNG

Four independent gates. Pin 4 = pin 5 OR pin 6 makes yellow from Q5 and Q4. The other three gates are not needed, but their six inputs still go to +5V or GND; their outputs stay open.

PinNameDirectionFunctionIn this circuit
1Ainputinput of the OR gate that drives pin 3tied to GND
2Binputinput of the OR gate that drives pin 3tied to GND
3JoutputJ = A OR Bunused
4KoutputK = C OR DYELLOW
5Cinputinput of the OR gate that drives pin 4Q5
6Dinputinput of the OR gate that drives pin 4Q4
7VSSpowerground, 0 VGND
8Einputinput of the OR gate that drives pin 10tied to +5V
9Finputinput of the OR gate that drives pin 10tied to +5V
10LoutputL = E OR Funused
11MoutputM = G OR Hunused
12Ginputinput of the OR gate that drives pin 11tied to +5V
13Hinputinput of the OR gate that drives pin 11tied to +5V
14VDDpowersupply, +5 V (3 to 18 V allowed)+5V

5Schematic 1: flip-flops and gates

STATE REGISTER: 5-BIT JOHNSON COUNTERNEXT STATEDECODER: 10 AND GATESOUTPUT LOGICDQRFF0rstDQRFF1rstDQRFF2rstDQRFF3rstDQRFF4rstthe inverted last bit goes back to the firstclkr0r0r1r1r2r2r3r3r4r4q0q1q2q3q4q5q6q7q8q9redyellowgreendot: wires connectedbubble: inverted inputrstto R of every flip-flop
The whole circuit as flip-flops and gates, without chips. This is what traffic_light.sv describes. · SVG · PNG

Read it as a state machine:

The first three blocks are what is inside the 4017; on the breadboard you only wire the fourth.

6Schematic 2: connections between the chips

1234567891011121314407112345678910111213141516Q5Q1Q0Q2Q6Q7Q3VSSQ8Q4Q9COINHCLKRSTVDD4017decade counter12345678910111213144072Q3Q1Q0Q2Q7Q6Q8Q9Q5Q4RESETCLOCK 555RGY+5VGND
The same circuit as chips, pin by pin. Each chip is drawn from above with the notch on the left; pin numbers are next to the pins. Wires have the same colours as on the breadboard. The labelled stubs lead to the modules: the reset button and the clock on the 4017, the three lamps of the traffic light on the OR outputs. · SVG · PNG

Every connection of the circuit, net by net. traffic_light_chips.sv in section 10 is written from the same list.

NetFromToWire
CLK555 clock module, OUT4017 14 (CLK)M-F
RSTreset button, OUT4017 15 (RST)M-F
Q04017 3 (Q0)4072 4 (C)M-M
Q14017 2 (Q1)4072 3 (B)M-M
Q24017 4 (Q2)4072 5 (D)M-M
Q34017 7 (Q3)4072 2 (A)M-M
Q44017 10 (Q4)4071 6 (D)M-M
Q54017 1 (Q5)4071 5 (C)M-M
Q64017 5 (Q6)4072 10 (F)M-M
Q74017 6 (Q7)4072 9 (E)M-M
Q84017 9 (Q8)4072 11 (G)M-M
Q94017 11 (Q9)4072 12 (H)M-M
RED4072 1 (J)traffic light RM-F
YELLOW4071 4 (K)traffic light YM-F
GREEN4072 13 (K)traffic light GM-F
+5V+5V4071 14 (VDD), 4017 16 (VDD), 4072 14 (VDD); unused inputs: 4071 13 (H), 4071 12 (G), 4071 9 (F), 4071 8 (E)
GNDGND4071 7 (VSS), 4017 8 (VSS), 4072 7 (VSS); unused inputs: 4071 1 (A), 4071 2 (B), 4017 13 (INH)
not connected4017 12 (CO), 4071 3 (J), 4071 10 (L), 4071 11 (M), 4072 6 (NC), 4072 8 (NC)

7Schematic 3: the breadboard

−+−+1234567891011121314151617181920212223242526272829303132333435363738394041424344abcdefghij4071401740725V5VPOWER MODULE · 5V9V USB-DCQ3Q1Q0Q2Q7Q6Q8Q9Q5Q4GREEN+OUT−RESETbutton−OUT+555CLOCK 555~1 HzGNDGYRTRAFFIC LIGHT
Every wire in its hole. Column numbers are printed along the top; rows a–e are above the gap, f–j below it. The reset button and the clock module hang above the board, the traffic light below it, all on male-female jumpers. The board goes on to column 63 on the right. · SVG · PNG
CLKRSTQ0Q1Q2Q3Q4Q5Q6Q7Q8Q9REDYELLOWGREEN+5VGND

Build it step by step

  1. Do the getting-started steps first: how the breadboard is connected inside, the power module with both jumpers on 5 V, how a chip sits across the gap.
  2. Insert the chips across the gap, notch to the left: the 4071 with pin 1 in hole 12f, the 4017 with pin 1 in 22f, the 4072 with pin 1 in 33f. Check the numbers on the chips.
  3. Power: a red wire from pin 14 or 16 to the top + rail and a black wire from pin 7 or 8 to the bottom − rail, for each chip. Then the tie-offs of the unused inputs. Make sure no wire touches + and − at the same time.
  4. The eleven signal wires, in the order of the checklist below. Tick each wire as you insert it.
  5. The modules, with the power off: the reset button and the clock module above the board, the traffic light below it. Connect GND of the traffic light first.
  6. Turn the power on and set the clock module to about 1 Hz with its potentiometer.

Wiring checklist

·

#NetFromTo
Power for the chips (red to +, black to −) · 6 wires
1+5V12a (4071 pin 14, VDD)top + rail, column 12
2GND18j (4071 pin 7, VSS)bottom − rail, column 18
3+5V22a (4017 pin 16, VDD)top + rail, column 22
4GND29j (4017 pin 8, VSS)bottom − rail, column 29
5+5V33a (4072 pin 14, VDD)top + rail, column 33
6GND39j (4072 pin 7, VSS)bottom − rail, column 39
Unused inputs: CMOS inputs must not float · 7 wires
7GND12j (4071 pin 1, A)bottom − rail, column 12
8GND13j (4071 pin 2, B)bottom − rail, column 13
9+5V13a (4071 pin 13, H)top + rail, column 13
10+5V14a (4071 pin 12, G)top + rail, column 14
11+5V17a (4071 pin 9, F)top + rail, column 17
12+5V18a (4071 pin 8, E)top + rail, column 18
13GND25a (4017 pin 13, INH)top − rail, column 25
Signal wires · 11 wires
14Q328g (4017 pin 7, Q3)34g (4072 pin 2, A)
15Q123h (4017 pin 2, Q1)35h (4072 pin 3, B)
16Q024i (4017 pin 3, Q0)36i (4072 pin 4, C)
17Q225j (4017 pin 4, Q2)37j (4072 pin 5, D)
18Q727j (4017 pin 6, Q7)38c (4072 pin 9, E)
19Q626g (4017 pin 5, Q6)37d (4072 pin 10, F)
20Q829c (4017 pin 9, Q8)36c (4072 pin 11, G)
21Q927d (4017 pin 11, Q9)35d (4072 pin 12, H)
22Q522i (4017 pin 1, Q5)16i (4071 pin 5, C)
23Q428c (4017 pin 10, Q4)17g (4071 pin 6, D)
24GREEN34b (4072 pin 13, K)11g
Reset button and clock module above the board (male-female jumpers) · 6 wires
25+5Vreset button, +top + rail, column 19
26RSTreset button, OUT23a (4017 pin 15, RST)
27GNDreset button, −top − rail, column 21
28+5Vclock module, +top + rail, column 28
29CLKclock module, OUT24a (4017 pin 14, CLK)
30GNDclock module, −top − rail, column 26
Traffic-light module below the board (male-female jumpers) · 4 wires
31GREENtraffic light, G11j
32YELLOWtraffic light, Y15j (4071 pin 4, K)
33REDtraffic light, R33j (4072 pin 1, J)
34GNDtraffic light, GNDbottom − rail, column 9

Test it

If it does not work: a lamp that never lights usually means a wire one hole off; a lamp that is always on, an input of the OR gate tied to +5V by mistake. Two lamps lit at once means two outputs are joined. If the light stops, check pin 13 of the 4017 (must be GND) and pin 15 (GND when the button is not pressed). If the chips get warm, turn the power off at once and look for a short between + and −.

8SystemVerilog: the generic design

traffic_light.sv describes schematic 1: the Johnson register is the always_ff block, the decoder is ten assign statements with AND, and the output logic is three with OR. It is synthesizable: an FPGA or an ASIC flow turns it into five flip-flops and a few gates. The output q shows the state, like the ten outputs of the 4017, so that the testbench and the boards can watch it.

traffic_light.svfile
// Exercise M11: a traffic light from a decade counter and OR gates,
// after Timofei Nikiforov's breadboard design.
//
// Generic version that matches schematic 1: five D flip-flops and gates,
// not tied to any chip.  Synthesizable.

module traffic_light
(
    input  logic       clk,
    input  logic       rst,     // 1 = go to state 0 at once, like RESET of the 4017

    output logic [9:0] q,       // q [k] = 1 in state k, like outputs Q0-Q9 of the 4017
    output logic       red,
    output logic       yellow,
    output logic       green
);

    //------------------------------------------------------------------------
    // State register: a 5-bit Johnson counter (twisted ring counter).
    // Every clock it shifts left and feeds the inverted top bit into bit 0:
    //
    // state  0     1     2     3     4     5     6     7     8     9
    // r      00000 00001 00011 00111 01111 11111 11110 11100 11000 10000
    //
    // Five flip-flops give ten states, and one bit changes per clock.

    logic [4:0] r;

    always_ff @ (posedge clk or posedge rst)
        if (rst)
            r <= '0;
        else
            r <= { r [3:0], ~ r [4] };

    //------------------------------------------------------------------------
    // Decoder: ten 2-input AND gates.  Two neighbouring bits identify each state.

    assign q [0] = ~ r [4] & ~ r [0];
    assign q [1] =   r [0] & ~ r [1];
    assign q [2] =   r [1] & ~ r [2];
    assign q [3] =   r [2] & ~ r [3];
    assign q [4] =   r [3] & ~ r [4];
    assign q [5] =   r [4] &   r [0];
    assign q [6] = ~ r [0] &   r [1];
    assign q [7] = ~ r [1] &   r [2];
    assign q [8] = ~ r [2] &   r [3];
    assign q [9] = ~ r [3] &   r [4];

    //------------------------------------------------------------------------
    // Output logic: OR gates

    assign red    = q [0] | q [1] | q [2] | q [3];  // 4 clock cycles
    assign yellow = q [4] | q [5];                  // 2 clock cycles
    assign green  = q [6] | q [7] | q [8] | q [9];  // 4 clock cycles

endmodule

{ r [3:0], ~ r [4] } is a concatenation: the lower four bits move one place up and the inverted top bit comes in at the bottom. This one line is the shift register with its NOT gate.

9SystemVerilog models of the chips

One module per chip, with ports named as in the datasheet and the pin number of each port in a comment. All three models are synthesizable. The models leave out the power pins: in SystemVerilog a wire simply has the value 0 or 1.

cd4017.svfile
// CD4017B: CMOS decade counter with 10 decoded outputs.
//
// Model of the chip as seen at its pins.  The power pins, VDD (16) and VSS (8),
// are left out.  Synthesizable.
//
// The chip counts on a rising edge of CLOCK while CLOCK INHIBIT is low, and
// also on a falling edge of CLOCK INHIBIT while CLOCK is high: CLOCK INHIBIT
// gates the clock.  The model does the same with an AND gate in the clock path.
// In an FPGA design keep clock_inhibit at 0; then the gate is just a wire.
//
// Inside, the counter is a 5-stage Johnson counter with a decoder, the same
// circuit as traffic_light.sv.  The real chip also has extra gates that bring
// the counter into the ten-state sequence from any state after power-up;
// the model needs a reset instead.

module cd4017
(
    input  logic       clock,          // Pin 14
    input  logic       clock_inhibit,  // Pin 13: 1 = hold the count
    input  logic       reset,          // Pin 15: 1 = count 0 (Q0 = 1) at once

    output logic [9:0] q,              // Pins 3 2 4 7 10 1 5 6 9 11 = Q0 ... Q9
    output logic       carry_out       // Pin 12: 1 for counts 0-4, 0 for counts 5-9
);

    wire gated_clock = clock & ~ clock_inhibit;

    logic [4:0] r;

    always_ff @ (posedge gated_clock or posedge reset)
        if (reset)
            r <= '0;
        else
            r <= { r [3:0], ~ r [4] };

    assign q [0] = ~ r [4] & ~ r [0];
    assign q [1] =   r [0] & ~ r [1];
    assign q [2] =   r [1] & ~ r [2];
    assign q [3] =   r [2] & ~ r [3];
    assign q [4] =   r [3] & ~ r [4];
    assign q [5] =   r [4] &   r [0];
    assign q [6] = ~ r [0] &   r [1];
    assign q [7] = ~ r [1] &   r [2];
    assign q [8] = ~ r [2] &   r [3];
    assign q [9] = ~ r [3] &   r [4];

    assign carry_out = ~ r [4];

endmodule

The 4017 model has one detail you may not expect: gated_clock. The real chip counts on the rising edge of CLOCK while CLOCK INHIBIT is low, and also on the falling edge of CLOCK INHIBIT while CLOCK is high; an AND gate in the clock path does exactly that. On an FPGA, clocks should not pass through gates, so keep clock_inhibit at 0; synthesis then removes the gate. The real chip also recovers from any state at power-up; the model needs a reset.

cd4071.svfile
// CD4071B: quad 2-input OR gate.
//
// Ports in pin order, named as in the datasheet.  The power pins, VSS (7)
// and VDD (14), are left out.  Synthesizable.

module cd4071
(
    input  logic a,  // Pin 1
    input  logic b,  // Pin 2
    output logic j,  // Pin 3:  j = a | b
    output logic k,  // Pin 4:  k = c | d
    input  logic c,  // Pin 5
    input  logic d,  // Pin 6
                     // Pin 7:  VSS
    input  logic e,  // Pin 8
    input  logic f,  // Pin 9
    output logic l,  // Pin 10: l = e | f
    output logic m,  // Pin 11: m = g | h
    input  logic g,  // Pin 12
    input  logic h   // Pin 13
                     // Pin 14: VDD
);

    assign j = a | b;
    assign k = c | d;
    assign l = e | f;
    assign m = g | h;

endmodule
cd4072.svfile
// CD4072B: dual 4-input OR gate.
//
// Ports in pin order, named as in the datasheet.  The power pins, VSS (7)
// and VDD (14), are left out, and so are the unconnected pins 6 and 8.
// Synthesizable.

module cd4072
(
    output logic j,  // Pin 1:  j = a | b | c | d
    input  logic a,  // Pin 2
    input  logic b,  // Pin 3
    input  logic c,  // Pin 4
    input  logic d,  // Pin 5
                     // Pin 6:  not connected
                     // Pin 7:  VSS
                     // Pin 8:  not connected
    input  logic e,  // Pin 9
    input  logic f,  // Pin 10
    input  logic g,  // Pin 11
    input  logic h,  // Pin 12
    output logic k   // Pin 13: k = e | f | g | h
                     // Pin 14: VDD
);

    assign j = a | b | c | d;
    assign k = e | f | g | h;

endmodule

10SystemVerilog: the chip-level design

traffic_light_chips.sv connects the chip models exactly as schematic 2 and the breadboard connect the chips, with the same ports as traffic_light.sv. Unused inputs are tied to vdd or gnd as on the breadboard, and unused outputs go to wires named unused_…. The file is generated from the breadboard wire list after a pin-level check of that list, so the code, the drawings and the checklist cannot disagree.

traffic_light_chips.svfile
// Exercise M11, chip-level version: the chips and wires of schematic 2 and
// of the breadboard.  Same ports as traffic_light.sv.
//
// Generated by tools/bb_m11.py from the checked breadboard wire list;
// change the wire list there, not this file.  Synthesizable.

module traffic_light_chips
(
    input  logic       clk,
    input  logic       rst,

    output logic [9:0] q,
    output logic       red,
    output logic       yellow,
    output logic       green
);

    // The power rails of the breadboard, for tied-off inputs

    wire gnd = 1'b0;
    wire vdd = 1'b1;

    // Outputs that go nowhere

    wire unused_4071_j;
    wire unused_4071_l;
    wire unused_4071_m;
    wire unused_4017_carry_out;

    //------------------------------------------------------------------------

    // CD4071 quad 2-input OR, pin 1 in column 12

    cd4071 i_4071
    (
        .a ( gnd           ),  // Pin 1 <- GND, unused input
        .b ( gnd           ),  // Pin 2 <- GND, unused input
        .j ( unused_4071_j ),  // Pin 3, unused output
        .k ( yellow        ),  // Pin 4 -> traffic light Y
        .c ( q [5]         ),  // Pin 5 <- 4017 pin 1 (Q5)
        .d ( q [4]         ),  // Pin 6 <- 4017 pin 10 (Q4)
        .e ( vdd           ),  // Pin 8 <- +5V, unused input
        .f ( vdd           ),  // Pin 9 <- +5V, unused input
        .l ( unused_4071_l ),  // Pin 10, unused output
        .m ( unused_4071_m ),  // Pin 11, unused output
        .g ( vdd           ),  // Pin 12 <- +5V, unused input
        .h ( vdd           )   // Pin 13 <- +5V, unused input
    );

    //------------------------------------------------------------------------

    // CD4017 decade counter, pin 1 in column 22

    cd4017 i_4017
    (
        .clock         ( clk                   ),  // Pin 14 <- the 555 clock module
        .clock_inhibit ( gnd                   ),  // Pin 13 <- GND
        .reset         ( rst                   ),  // Pin 15 <- the reset button
        .q             ( q                     ),  // Pins 3 2 4 7 10 1 5 6 9 11 = Q0 ... Q9
        .carry_out     ( unused_4017_carry_out )   // Pin 12, unused output
    );

    //------------------------------------------------------------------------

    // CD4072 dual 4-input OR, pin 1 in column 33

    cd4072 i_4072
    (
        .j ( red   ),  // Pin 1 -> traffic light R
        .a ( q [3] ),  // Pin 2 <- 4017 pin 7 (Q3)
        .b ( q [1] ),  // Pin 3 <- 4017 pin 2 (Q1)
        .c ( q [0] ),  // Pin 4 <- 4017 pin 3 (Q0)
        .d ( q [2] ),  // Pin 5 <- 4017 pin 4 (Q2)
        .e ( q [7] ),  // Pin 9 <- 4017 pin 6 (Q7)
        .f ( q [6] ),  // Pin 10 <- 4017 pin 5 (Q6)
        .g ( q [8] ),  // Pin 11 <- 4017 pin 9 (Q8)
        .h ( q [9] ),  // Pin 12 <- 4017 pin 11 (Q9)
        .k ( green )   // Pin 13 -> traffic light G
    );

endmodule

11Testbench and simulation scripts

tb.sv runs both designs side by side from the same clock and reset and compares them with a reference model: a state number from 0 to 9 and the colour that belongs to it. It checks at every falling clock edge, half a period after the state has changed, and also tests an asynchronous reset in the middle of a state. At the end it counts the clock periods of each colour after a reset and prints PASS or FAIL.

tb.sv192 lines · show the codefile
// Testbench for exercise M11.
//
// Runs the generic design (traffic_light.sv) and the chip-level design
// (traffic_light_chips.sv) side by side and compares both with a reference
// model: a state number from 0 to 9 and the colour that belongs to it.
// Works with Icarus Verilog, Verilator (--binary) and Questa.

module tb;

    logic       clk;
    logic       rst;

    logic [9:0] q,      q_chips;
    logic       red,    red_chips;
    logic       yellow, yellow_chips;
    logic       green,  green_chips;

    traffic_light i_generic
    (
        .clk    ( clk    ),
        .rst    ( rst    ),
        .q      ( q      ),
        .red    ( red    ),
        .yellow ( yellow ),
        .green  ( green  )
    );

    traffic_light_chips i_chips
    (
        .clk    ( clk          ),
        .rst    ( rst          ),
        .q      ( q_chips      ),
        .red    ( red_chips    ),
        .yellow ( yellow_chips ),
        .green  ( green_chips  )
    );

    //------------------------------------------------------------------------
    // Clock with a period of 10 time units.
    // On the breadboard one period is about a second.

    initial
    begin
        clk = 1'b0;

        forever
            # 5 clk = ~ clk;
    end

    //------------------------------------------------------------------------
    // Reference model: the number of the state

    logic [3:0] state;

    always @ (posedge clk or posedge rst)
        if (rst)
            state <= 4'd0;
        else if (state == 4'd9)
            state <= 4'd0;
        else
            state <= state + 4'd1;

    wire [9:0] q_expected      = 10'b1 << state;
    wire       red_expected    = state <= 4'd3;
    wire       yellow_expected = state == 4'd4 || state == 4'd5;
    wire       green_expected  = state >= 4'd6;

    //------------------------------------------------------------------------
    // Checks at every falling edge, half a period after the state changes

    int cycle, n_checks, n_errors;

    function automatic string light_name (input logic r, input logic y, input logic g);
        case ({ r, y, g })
        3'b100:  return "red   ";
        3'b010:  return "yellow";
        3'b001:  return "green ";
        3'b000:  return "dark  ";
        default: return "mixed ";
        endcase
    endfunction

    function automatic logic is_correct
    (
        input logic [9:0] q_actual,
        input logic       r,
        input logic       y,
        input logic       g
    );
        return    q_actual === q_expected
               && r        === red_expected
               && y        === yellow_expected
               && g        === green_expected;
    endfunction

    // Clock cycles of each colour during the first full period after reset

    int n_red, n_yellow, n_green, n_period;

    initial
    begin
        cycle    = 0;
        n_checks = 0;
        n_errors = 0;
        n_red    = 0;
        n_yellow = 0;
        n_green  = 0;
        n_period = 0;

        forever
        begin
            logic ok_generic, ok_chips;

            @ (negedge clk);
            cycle ++;

            ok_generic = is_correct (q,       red,       yellow,       green);
            ok_chips   = is_correct (q_chips, red_chips, yellow_chips, green_chips);

            n_checks += 2;

            if (! ok_generic)
                n_errors ++;

            if (! ok_chips)
                n_errors ++;

            $display ("%5d  %b    %b  %s  %s  %s",
                cycle, rst, q, light_name (red, yellow, green),
                ok_generic ? "ok     " : "WRONG  ", ok_chips ? "ok" : "WRONG");

            if (! rst && n_period < 10)
            begin
                n_period ++;

                if (red)
                    n_red ++;

                if (yellow)
                    n_yellow ++;

                if (green)
                    n_green ++;
            end
        end
    end

    //------------------------------------------------------------------------
    // Stimulus

    initial
    begin
        $dumpfile ("tb.vcd");

        `ifdef __ICARUS__
            $dumpvars (0, tb);  // Without a scope Icarus also tries to dump $unit
        `else
            $dumpvars;
        `endif

        $display ("cycle  rst  Q9..Q0      light   generic  chips");

        // Reset at the start, released between two clock edges

        rst = 1'b1;
        # 12 rst = 1'b0;

        repeat (24) @ (posedge clk);

        // Reset in the middle of a state: both designs go to state 0 at once,
        // without waiting for the clock, like RESET of the 4017

        # 2 rst = 1'b1;
        repeat (2) @ (posedge clk);
        # 7 rst = 1'b0;

        repeat (12) @ (posedge clk);
        @ (negedge clk);
        # 1;  // Let the last check print first

        $display ("One period after reset: red %0d, yellow %0d, green %0d clock cycles",
            n_red, n_yellow, n_green);

        if (n_errors == 0 && n_red == 4 && n_yellow == 2 && n_green == 4)
            $display ("PASS: %0d checks, no errors", n_checks);
        else
            $display ("FAIL: %0d errors in %0d checks", n_errors, n_checks);

        $finish;
    end

endmodule

Each script compiles the six files, runs the simulation, writes a log and the waveform tb.vcd, and exits with an error unless the test passes. Run them from any directory: ./run_icarus.bash, ./run_verilator.bash or ./run_questa.bash; ./clean.bash removes what they make. The waveform opens in GTKWave or Surfer.

run_icarus.bash22 lines · show the codefile
#!/usr/bin/env bash

# Exercise M11: run the testbench with Icarus Verilog 12 or later.
# The output goes to icarus.log and the waveform to tb.vcd,
# for GTKWave or Surfer.

set -Eeuo pipefail

cd "$(dirname "$0")"

files=(tb.sv traffic_light.sv traffic_light_chips.sv cd4017.sv cd4071.sv cd4072.sv)

if ! command -v iverilog > /dev/null
then
    printf '%s\n' "$0: iverilog not found, install Icarus Verilog 12 or later" >&2
    exit 1
fi

iverilog -g2012 -o tb.vvp "${files[@]}"
vvp -n tb.vvp | tee icarus.log

grep -q '^PASS' icarus.log
run_verilator.bash29 lines · show the codefile
#!/usr/bin/env bash

# Exercise M11: run the testbench with Verilator 5.  --binary builds
# a simulator with the testbench's delays; it needs a C++20 compiler.
# The output goes to verilator.log and the waveform to tb.vcd.

set -Eeuo pipefail

cd "$(dirname "$0")"

files=(tb.sv traffic_light.sv traffic_light_chips.sv cd4017.sv cd4071.sv cd4072.sv)

# OSS CAD Suite unpacked in the home directory has Verilator too

if ! command -v verilator > /dev/null && [ -x "$HOME/oss-cad-suite/bin/verilator" ]
then
    PATH="$HOME/oss-cad-suite/bin:$PATH"
fi

if ! command -v verilator > /dev/null
then
    printf '%s\n' "$0: verilator not found, install Verilator 5" >&2
    exit 1
fi

verilator --binary -Wall --trace --top-module tb -o tb "${files[@]}"
obj_dir/tb | tee verilator.log

grep -q '^PASS' verilator.log
run_questa.bash64 lines · show the codefile
#!/usr/bin/env bash

# Exercise M11: run the testbench with Questa, for example the free
# Questa Starter Edition that comes with Quartus Prime Lite.
# The output goes to questa.log and the waveform to tb.vcd.

set -Eeuo pipefail

cd "$(dirname "$0")"

files=(tb.sv traffic_light.sv traffic_light_chips.sv cd4017.sv cd4071.sv cd4072.sv)

# When vsim is not in PATH, look for it where Quartus installs it

if ! command -v vsim > /dev/null
then
    for dir in "$HOME"/intelFPGA_lite/*/questa_fse/bin  \
               "$HOME"/altera_lite/*/questa_fse/bin     \
               /opt/intelFPGA_lite/*/questa_fse/bin     \
               /opt/altera_lite/*/questa_fse/bin
    do
        if [ -x "$dir/vsim" ]
        then
            PATH="$dir:$PATH"
            break
        fi
    done
fi

if ! command -v vsim > /dev/null
then
    printf '%s\n' "$0: vsim not found, install Questa or add it to PATH" >&2
    exit 1
fi

# The Starter Edition needs a free license file from the Intel
# licensing site; Quartus keeps it in ~/flexlm/license.dat

license="$HOME/flexlm/license.dat"

# ${SALT_LICENSE_SERVER-} is empty when the variable is not set,
# where a plain $SALT_LICENSE_SERVER would stop the script (set -u)
salt_license="${SALT_LICENSE_SERVER-}"

if [ -z "$salt_license" ] && [ -f "$license" ]
then
    export SALT_LICENSE_SERVER="$license"
fi

# Older Questa versions read LM_LICENSE_FILE; ${LM_LICENSE_FILE-}
# is empty when the variable is not set
lm_license="${LM_LICENSE_FILE-}"

if [ -z "$lm_license" ] && [ -f "$license" ]
then
    export LM_LICENSE_FILE="$license"
fi

rm -rf work
vlib work > /dev/null
vlog -sv -lint "${files[@]}"
vsim -c tb -do "run -all; quit -f" | tee questa.log

grep -q 'PASS' questa.log

Tested with Icarus Verilog 12.0 and 14.0 (development), Verilator 5.053 with -Wall and no warnings, and Questa Altera Starter FPGA Edition 2025.2 from Quartus Prime Lite 25.1. The code uses only what Icarus Verilog 13 supports. This is the output, from Icarus Verilog:

VCD info: dumpfile tb.vcd opened for output.
cycle  rst  Q9..Q0      light   generic  chips
    1  1    0000000001  red     ok          ok
    2  0    0000000010  red     ok          ok
    3  0    0000000100  red     ok          ok
    4  0    0000001000  red     ok          ok
    5  0    0000010000  yellow  ok          ok
    6  0    0000100000  yellow  ok          ok
    7  0    0001000000  green   ok          ok
    8  0    0010000000  green   ok          ok
    9  0    0100000000  green   ok          ok
   10  0    1000000000  green   ok          ok
   11  0    0000000001  red     ok          ok
   12  0    0000000010  red     ok          ok
   13  0    0000000100  red     ok          ok
   14  0    0000001000  red     ok          ok
   15  0    0000010000  yellow  ok          ok
   16  0    0000100000  yellow  ok          ok
   17  0    0001000000  green   ok          ok
   18  0    0010000000  green   ok          ok
   19  0    0100000000  green   ok          ok
   20  0    1000000000  green   ok          ok
   21  0    0000000001  red     ok          ok
   22  0    0000000010  red     ok          ok
   23  0    0000000100  red     ok          ok
   24  0    0000001000  red     ok          ok
   25  1    0000000001  red     ok          ok
   26  1    0000000001  red     ok          ok
   27  1    0000000001  red     ok          ok
   28  0    0000000010  red     ok          ok
   29  0    0000000100  red     ok          ok
   30  0    0000001000  red     ok          ok
   31  0    0000010000  yellow  ok          ok
   32  0    0000100000  yellow  ok          ok
   33  0    0001000000  green   ok          ok
   34  0    0010000000  green   ok          ok
   35  0    0100000000  green   ok          ok
   36  0    1000000000  green   ok          ok
   37  0    0000000001  red     ok          ok
   38  0    0000000010  red     ok          ok
   39  0    0000000100  red     ok          ok
One period after reset: red 4, yellow 2, green 4 clock cycles
PASS: 78 checks, no errors
tb.sv:189: $finish called at 391 (1s)

To see that the testbench really catches mistakes, break something and run it again: connect pin 2 of the 4072 to q [4] instead of q [3] in traffic_light_chips.sv, or change one decoder line in traffic_light.sv. The script reports FAIL and the trace shows the lines marked WRONG.

12On an FPGA board: lab_top in BGM

The Basics-Graphics-Music (BGM) repository runs the same labs on dozens of FPGA boards. Each lab has a lab_top module with the same ports on every board: clock, a 1 Hz slow_clk, reset, keys, LEDs, a 7-segment display.

  1. Copy a lab, for example labs/1_basics/1_06_binary_counter, to a new directory such as labs/1_basics/1_11_traffic_light.
  2. Replace its lab_top.sv with bgm_lab/lab_top.sv from this exercise and add traffic_light.sv. The BGM scripts compile every .sv file of the directory except tb.sv.
  3. Run ./03_synthesize_for_fpga.bash and ./04_configure_fpga.bash; the first run asks which board you have.

The traffic light runs from slow_clk, which every BGM board makes at 1 Hz, so it steps once a second like the breadboard. BGM makes it in peripherals/slow_clk_gen.sv: a counter on the board clock toggles a flip-flop, and on AMD (Xilinx) and Intel (Altera) FPGAs a BUFG or global primitive puts that signal on the FPGA clock network, so it reaches all flip-flops like a real clock. The rightmost digit of the display is a small traffic light: segment a (top) is red, g (middle) yellow, d (bottom) green. The LEDs show the state as a running light. To run the chip-level design instead, add traffic_light_chips.sv and the three chip models and change the module name in the instance.

bgm_lab/lab_top.sv117 lines · show the codefile
// Exercise M11 on an FPGA board with the Basics-Graphics-Music (BGM) labs:
// https://github.com/yuri-panchul/basics-graphics-music
//
// Copy a lab directory, for example labs/1_basics/1_06_binary_counter,
// replace its lab_top.sv with this file and add traffic_light.sv.
// For the chip-level design add traffic_light_chips.sv, cd4017.sv, cd4071.sv
// and cd4072.sv instead, and change the module name below.
//
// The traffic light runs from slow_clk, the 1 Hz clock that every BGM board
// wrapper makes, so it changes state once a second, like the breadboard with
// the 555 module.  Reset is the reset button of the board.
// The 7-segment display shows the light: segment a red, g yellow, d green.
// The LEDs show the state as a running light.

`include "config.svh"

module lab_top
# (
    parameter  clk_mhz       = 50,
               w_key         = 4,
               w_sw          = 8,
               w_led         = 8,
               w_digit       = 8,
               w_gpio        = 100,

               screen_width  = 640,
               screen_height = 480,

               w_red         = 4,
               w_green       = 4,
               w_blue        = 4,

               w_x           = $clog2 ( screen_width  ),
               w_y           = $clog2 ( screen_height )
)
(
    input                        clk,
    input                        slow_clk,
    input                        rst,

    // Keys, switches, LEDs

    input        [w_key   - 1:0] key,
    input        [w_sw    - 1:0] sw,
    output logic [w_led   - 1:0] led,

    // A dynamic seven-segment display

    output logic [          7:0] abcdefgh,
    output logic [w_digit - 1:0] digit,

    // Graphics

    input        [w_x     - 1:0] x,
    input        [w_y     - 1:0] y,

    output logic [w_red   - 1:0] red,
    output logic [w_green - 1:0] green,
    output logic [w_blue  - 1:0] blue,

    // Microphone, sound output and UART

    input        [         23:0] mic,
    output       [         15:0] sound,

    input                        uart_rx,
    output                       uart_tx,

    // General-purpose Input/Output

    inout        [w_gpio  - 1:0] gpio
);

    //------------------------------------------------------------------------

       assign red        = '0;
       assign green      = '0;
       assign blue       = '0;
       assign sound      = '0;
       assign uart_tx    = '1;

    //------------------------------------------------------------------------

    // lab_top already has ports named red and green (the screen colours),
    // so the lights get other names here

    logic [9:0] q;
    logic       light_red, light_yellow, light_green;

    traffic_light i_traffic_light
    (
        .clk    ( slow_clk     ),
        .rst    ( rst          ),
        .q      ( q            ),
        .red    ( light_red    ),
        .yellow ( light_yellow ),
        .green  ( light_green  )
    );

    //------------------------------------------------------------------------

    //   --a--
    //  |     |
    //  f     b
    //  |     |
    //   --g--
    //  |     |
    //  e     c
    //  |     |
    //   --d--  h

    assign abcdefgh = { light_red, 2'b0, light_green, 2'b0, light_yellow, 1'b0 };
    assign digit    = w_digit' (1);

    assign led      = w_led' (q);

endmodule

13On an FPGA board: hackathon_top in BGM

The hackathon setup of BGM uses a Tang Nano 9K with a 480×272 screen and a TM1638 board of keys, LEDs and digits. Its top module, hackathon_top, has simpler ports; the first line of the file names the board, so the scripts need no questions.

  1. Copy a directory such as hackathon/problems/4_binary_counter.
  2. Replace its hackathon_top.sv with bgm_hackathon/hackathon_top.sv and add traffic_light.sv.
  3. Run ./03_synthesize_for_fpga.bash and ./04_configure_fpga.bash.

The screen shows a traffic light: three round lamps in a dark box, bright when on and dim when off. The rightmost digit shows the same three segments as in lab_top, and the eight LEDs show states 0–7. The screen colour ports are called red and green, so the lights are called light_red, light_yellow and light_green here.

bgm_hackathon/hackathon_top.sv112 lines · show the codefile
// Board configuration: tang_nano_9k_lcd_480_272_tm1638_hackathon
// This module uses few parameterization and relaxed typing rules

// Exercise M11 in the hackathon setup of Basics-Graphics-Music (BGM):
// https://github.com/yuri-panchul/basics-graphics-music
//
// Copy a directory such as hackathon/problems/4_binary_counter, replace its
// hackathon_top.sv with this file and add traffic_light.sv.
//
// The traffic light runs from slow_clock, one beat per second, like the
// breadboard with the 555 module.  The screen shows a traffic light,
// the 7-segment display shows it too (segment a red, g yellow, d green),
// and the eight LEDs show states 0-7 as a running light.

module hackathon_top
(
    input  logic       clock,
    input  logic       slow_clock,
    input  logic       reset,

    input  logic [7:0] key,
    output logic [7:0] led,

    // A dynamic seven-segment display

    output logic [7:0] abcdefgh,
    output logic [7:0] digit,

    // LCD screen interface

    input  logic [8:0] x,
    input  logic [8:0] y,

    output logic [4:0] red,
    output logic [5:0] green,
    output logic [4:0] blue,

    inout  logic [3:0] gpio
);

    //------------------------------------------------------------------------
    // The screen colours are called red and green already,
    // so the lights get other names

    logic [9:0] q;
    logic       light_red, light_yellow, light_green;

    traffic_light i_traffic_light
    (
        .clk    ( slow_clock   ),
        .rst    ( reset        ),
        .q      ( q            ),
        .red    ( light_red    ),
        .yellow ( light_yellow ),
        .green  ( light_green  )
    );

    assign led      = q [7:0];

    assign abcdefgh = { light_red, 2'b0, light_green, 2'b0, light_yellow, 1'b0 };
    assign digit    = 8'b0000_0001;

    //------------------------------------------------------------------------
    // A traffic light on the 480 x 272 screen: three round lamps in a dark box.
    // dx is the distance from the middle of the screen, dy1-dy3 the distances
    // from the centres of the lamps, so a lamp is where dx * dx + dy * dy < 34 * 34.

    wire [8:0] dx  = x > 240 ? x - 240 : 240 - x;
    wire [8:0] dy1 = y > 56  ? y - 56  : 56  - y;
    wire [8:0] dy2 = y > 136 ? y - 136 : 136 - y;
    wire [8:0] dy3 = y > 216 ? y - 216 : 216 - y;

    wire lamp1 = dx * dx + dy1 * dy1 < 34 * 34;
    wire lamp2 = dx * dx + dy2 * dy2 < 34 * 34;
    wire lamp3 = dx * dx + dy3 * dy3 < 34 * 34;

    always_comb
    begin
        red   = 0;
        green = 0;
        blue  = 0;

        if (x >= 196 & x < 284 & y >= 12 & y < 260)
        begin
            red   = 3;
            green = 6;
            blue  = 3;
        end

        if (lamp1)
        begin
            red   = light_red ? 31 : 7;
            green = 0;
            blue  = 0;
        end

        if (lamp2)
        begin
            red   = light_yellow ? 31 : 7;
            green = light_yellow ? 50 : 11;
            blue  = 0;
        end

        if (lamp3)
        begin
            red   = 0;
            green = light_green ? 63 : 14;
            blue  = light_green ? 8  : 2;
        end
    end

endmodule

14On a chip: Tiny Tapeout

Tiny Tapeout makes real chips from student designs. Each design is a module with the ports below; on the demo board ui_in comes from DIP switches and uo_out drives a 7-segment display.

The demo board makes the project clock itself and can set it anywhere from 1 Hz to 66.5 MHz (see Clock in the Tiny Tapeout docs). Set it to 1 Hz, and project.v connects clk straight to the traffic light, just as the 555 module clocks the 4017 on the breadboard. The display shows the light on segments a, g and d.

tiny_tapeout/project.v54 lines · show the codefile
/*
 * Exercise M11 on Tiny Tapeout: Timofei's traffic light on the 7-segment
 * display of the demo board.
 *
 * Add traffic_light.sv to source_files in info.yaml next to project.v.
 * Rename the module to tt_um_<your GitHub name>_<project name> and use the
 * same name as top_module in info.yaml.
 */

`default_nettype none

module tt_um_example (
    input  wire [7:0] ui_in,    // DIP switches, INPUT Pmod, RP2350 GPIO17-24
    output wire [7:0] uo_out,   // 7-segment display, OUTPUT Pmod, RP2350 GPIO33-40
    input  wire [7:0] uio_in,   // BIDIR Pmod, RP2350 GPIO25-32
    output wire [7:0] uio_out,  //   (same pins)
    output wire [7:0] uio_oe,   //   1 = your design drives the pin
    input  wire       ena,      // set by the on-chip project mux, not a board pin
    input  wire       clk,      // RP2350 GPIO16
    input  wire       rst_n     // RP2350 GPIO14
);

    // The light should change about once a second.  The demo board makes
    // clk itself and can set it anywhere from 1 Hz to 66.5 MHz, so set the
    // project clock to 1 Hz (clock_hz: 1 in info.yaml) and clk drives the
    // traffic light directly.  Do not make a slow clock from a counter in an
    // ASIC: keep one clock and use a clock enable when you need a slow tick.

    wire [9:0] q;
    wire       red, yellow, green;

    traffic_light i_traffic_light (
        .clk    ( clk      ),
        .rst    ( ~ rst_n  ),
        .q      ( q        ),
        .red    ( red      ),
        .yellow ( yellow   ),
        .green  ( green    )
    );

    // 7-segment display: uo_out [0] is segment a, ..., uo_out [6] is g,
    // uo_out [7] is the dot.  Segment a (top) is red, g (middle) yellow,
    // d (bottom) green.

    assign uo_out  = { 1'b0, yellow, 2'b0, green, 2'b0, red };

    assign uio_out = 8'b0;
    assign uio_oe  = 8'b0;

    // List all unused inputs to prevent warnings

    wire _unused = & { ena, ui_in, uio_in, q, 1'b0 };

endmodule

In info.yaml set the clock to 1 Hz, list both files and use the module name as the top module:

project:
  clock_hz:   1                 # the demo board makes a 1 Hz clock
  top_module: "tt_um_example"   # rename to tt_um_<your GitHub name>_traffic_light
  source_files:
    - "project.v"
    - "traffic_light.sv"
No clock divider on a chip. BGM makes slow_clk with a counter, but on AMD and Intel FPGAs it routes that signal onto the clock network with BUFG or global (section 12), and some production FPGA designs do the same. In an ASIC a counter bit used as a clock is bad practice: it is a second clock with its own clock tree, its edges come later than those of clk by a delay nobody controls, the timing tools must be told about it, and every signal between the two clocks crosses clock domains. Here the board makes the 1 Hz clock, so no divider is needed. When a chip needs a slow tick from a fast clock, keep one clock and add a clock enable (a task in section 15).

15Going further

Tasks for students

A shortcut that the chip hides

With the Johnson register visible, the decoder and the OR gates shrink to three 2-input gates: red is states 0–3, which is exactly ~ r [3] & ~ r [4]; yellow is r [3] & r [0]; green is r [4] & ~ r [0]. Check them in the state table of section 2. The 4017 cannot do this because its Johnson bits stay inside the chip, but in an FPGA it is a free optimization, and synthesis tools find such things on their own.

The 555-and-4017 traffic light has been built for decades, mostly with diodes. A few versions to compare:

Files of this exercise

Sources

Timofei Nikiforov's circuit is described in the Habr article about the electronics course (lesson 1.7) and in “Teaching children electronics is due for a major redesign”, picture 15. The 4017, 4071 and 4072 are described in the Texas Instruments datasheets for the CD4017B, CD4071B and CD4072B.