Упражнения на макетной плате/ M11EnglishРусский

Упражнение M11 · мини-проект · конечный автомат

Светофор на десятичном счётчике

Четыре секунды красный, две жёлтый, четыре зелёный, и снова. Школьник Тимофей Никифоров собрал этот светофор из десятичного счётчика 4017 и элементов ИЛИ. Это конечный автомат с десятью состояниями, идущими по кругу, а микросхема счётчика служит его регистром состояния.

3 микросхемысчётчик 4017, элементы ИЛИ 4072 и 4071
24 + 10 проводовна плате + перемычки к модулям
10 состояний4 красных, 2 жёлтых, 4 зелёных
такт ~1 Гцот модуля на 555, генератор собирать не нужно

1Что это и зачем

Что делает. Светофор, который переключается раз в такт: четыре такта красный, два жёлтый, четыре зелёный, потом снова красный. Если генератор настроен на 1 Гц, получается 4 секунды красного, 2 жёлтого и 4 зелёного. Школьник Тимофей Никифоров сам додумался до этой схемы и собрал её вместе с младшим братом Нилом (статья на Хабре, занятие 1.7, и «Teaching children electronics is due for a major redesign», рисунок 15).

Как работает. У 4017 десять выходов, от Q0 до Q9, и в любой момент высокий уровень ровно на одном из них: на Q0 после сброса, на Q1 после первого растущего фронта такта и так далее до Q9, затем снова Q0. Каждый выход — это одно состояние конечного автомата. Три элемента ИЛИ превращают состояния в свет:

красный = Q0 ИЛИ Q1 ИЛИ Q2 ИЛИ Q3
жёлтый  = Q4 ИЛИ Q5
зелёный = Q6 ИЛИ Q7 ИЛИ Q8 ИЛИ Q9

Классическая схема, но на логических элементах. Светофор на 4017 с генератором на 555 — известная радиолюбительская схема. В обычном варианте выходы 4017 объединяют диодами, а не элементами ИЛИ, и красный часто берут с выхода CARRY OUT, на котором единица при счёте 0–4 (см. похожие схемы в разделе 15). С элементами ИЛИ та же идея читается как логика: каждый свет — это ИЛИ от состояний, и именно так схема записана на SystemVerilog дальше на этой странице.

Где применяется. Автомат, у которого состояния идут по кругу в одном и том же порядке, по шагу за такт, — самый простой вид конечного автомата. В микросхемах таких автоматов много: контроллер, который проходит фазы пересылки (выставить адрес, подождать, забрать данные), устройство управления простого процессора (выборка, декодирование, исполнение, запись результата), драйвер индикатора, который зажигает разряды по очереди, round-robin планировщик, который даёт каждому запросу его очередь, таймер, который делит период на интервалы. В ПЛИС такие автоматы часто строят ровно так, как устроены выходы 4017: по триггеру на состояние, единица в каждый момент только в одном из них (унитарный код, one-hot), а выходы — это ИЛИ от состояний.

Зачем это изучать.

2Главный элемент: счётчик с дешифрированными выходами

S0S1S2S3S4S5S6S7S8S9rstкаждый фронт:шаг по часовой стрелке

Каждый растущий фронт такта сдвигает автомат на шаг по часовой стрелке; сброс сразу переводит его в S0. Цвет кружка — это свет, который горит в этом состоянии. Состояние меняется только по фронту такта, а свет зависит только от состояния: это автомат Мура.

Отдельная логика следующего состояния кругу не нужна: следующее состояние вычисляет сам счётчик. Проектировать надо только выходную логику, три элемента ИЛИ.

Внутри 4017 стоит не двоичный счётчик. Двоичному хватило бы четырёх триггеров на десять состояний, но у него за такт меняются сразу несколько битов (при переходе 7 → 8 меняются все четыре), и дешифратор на мгновение может показать не то состояние — это глитч.

Внутри 4017: счётчик Джонсона и дешифратор

В 4017 пять D-триггеров соединены в сдвиговый регистр, а последний бит возвращается в первый инвертированным. Начиная с 00000, регистр справа заполняется единицами, потом нулями, проходит десять комбинаций и за такт меняет ровно один бит. Каждую комбинацию узнают по двум соседним битам, поэтому на состояние хватает одного элемента И на два входа, и дешифратор не даёт глитчей.

СостояниеРегистр Джонсона r4…r0Дешифратор: q = 1, когдаQ9…Q0ПереносСвет
S000000~r0 & ~r400000000011красный
S100001r0 & ~r100000000101красный
S200011r1 & ~r200000001001красный
S300111r2 & ~r300000010001красный
S401111r3 & ~r400000100001жёлтый
S511111r0 & r400001000000жёлтый
S611110~r0 & r100010000000зелёный
S711100~r1 & r200100000000зелёный
S811000~r2 & r301000000000зелёный
S910000~r3 & r410000000000зелёный

Условные обозначения

D-триггер со сбросом

DQRDclkQrst
rstclkDQ после
1любойлюбой0, сразу
0↑00
0↑11
0нет фронталюбойхранит Q

Q принимает значение D по растущему фронту такта (↑) и хранит его до следующего фронта. R = 1 сразу обнуляет Q, не дожидаясь такта.

НЕ

AY
AY
01
10

Возвращает инвертированный последний бит в первый триггер. Кружок на входе или выходе элемента означает НЕ.

И с инверсным входом

ABY
ABY = A И НЕ B
000
010
101
110

По такому элементу на каждое состояние дешифратора, например q1 = r0 И НЕ r1.

ИЛИ на 2 и на 4 входа

ABYABCDY
ABY = A ИЛИ B
000
011
101
111

Y = 1, когда 1 хотя бы на одном входе. ИЛИ на 4 входа даёт 1, если единица хотя бы на одном из четырёх.

Десятичный счётчик 4017

4017CLKINHRSTQ0Q1Q2Q3Q4Q5Q6Q7Q8Q9CO

CLK считает по растущему фронту, INH = 1 останавливает счёт, RST = 1 ставит счёт в 0. Высокий уровень на одном из Q0–Q9; CO в состояниях 0–4 равен 1.

Временные диаграммы

clkrstr4…r000000000010001100111011111111111110111001100010000000000000100011001110111111111состояниеS0S1S2S3S4S5S6S7S8S9S0S1S2S3S4S5Q0Q1Q2Q3Q4Q5Q6Q7Q8Q9красныйжёлтыйзелёный
Из симуляции tb.sv в Icarus Verilog. Сброс снимается между двумя фронтами такта; дальше каждый растущий фронт (пунктир) сдвигает регистр Джонсона r и переводит автомат на шаг. Каждый выход Q0–Q9 держит единицу ровно один такт; красный горит четыре такта, жёлтый два, зелёный четыре. · SVG · PNG

3Детали

ДетальВ этой схемеКол-воЦенаПоставщик
CD4017десятичный счётчик: регистр состояния и дешифратор1$0.40Elexp (elexp.com)
CD4072два элемента ИЛИ на 4 входа: красный и зелёный1$0.25Elexp (elexp.com)
CD4071четыре элемента ИЛИ на 2 входа: жёлтый (нужен один)1$0.25Elexp (elexp.com)
Генератор на 555тактовый сигнал, около 1 Гц1$0.53aliexpress.us
Модуль кнопкисброс; один модуль из набора из пяти1$0.66 / наборaliexpress.us
Модуль светофора на 5 Втри лампы, резисторы уже на плате1$0.40aliexpress.us
Макетная плата на 830 точек1$3.00amazon.com
Блок питания для макетной платы5 В на шинах1$1.00aliexpress.us
Кабель USB-DC на 9 Впитание блока от USB-зарядки1$1.17aliexpress.us
Нарезанные перемычки24 проводов на плате24$11.95 / наборjameco.com
Перемычки Dupont папа-мама10 проводов к трём модулям10$1.30 / наборaliexpress.us

Цены из таблицы компонентов LSSK; упражнение входит в полный набор (Comprehensive kit). Кнопка сброса не обязательна: без неё соедините ножку 15 микросхемы 4017 с GND. В 4017 есть дополнительные элементы, которые после включения питания из любого состояния выводят счётчик на круг из десяти состояний, так что светофор начнёт со случайного цвета и дальше пойдёт как обычно.

Покупайте модуль светофора на 5 В. Такой же модуль продаётся и в версии на 1 В, и внешне их легко спутать; об этом предупреждает занятие 1.7 статьи на Хабре. Версию на 5 В подключают прямо к выходам микросхем, как здесь.

4Микросхемы, ножка за ножкой

Все три микросхемы — КМОП серии 4000B: работают от 3 до 18 В, здесь от 5 В блока питания. Каждый вход должен быть куда-то подключён: висящий вход КМОП ловит наводки и может переключаться сам по себе, поэтому неиспользуемые входы 4071 подключены к +5V или GND. Все выходы здесь с активным высоким уровнем: 1 — это +5 В и горящая лампа. Микросхемы нарисованы сверху, как они стоят на макетной плате: выемка слева, ножка 1 внизу слева, нумерация против часовой стрелки. Синие стрелки — входы, оранжевые — выходы.

4017: десятичный счётчик с 10 дешифрированными выходами

1Q52Q13Q04Q25Q66Q77Q38VSS9Q810Q411Q912CO13INH14CLK15RST16VDD4017десятичный счётчик
CD4017B. Треугольник у CLOCK означает вход, который срабатывает по фронту. · SVG · PNG

Активные уровни. Счёт идёт по растущему фронту CLOCK, пока CLOCK INHIBIT в нуле. RESET активен единицей и асинхронный: пока он равен 1, счёт равен 0 и на Q0 высокий уровень, что бы ни происходило с тактом. CLOCK INHIBIT активен единицей: 1 останавливает счёт.

4017 считает и по падающему фронту CLOCK INHIBIT, если CLOCK в этот момент в единице: CLOCK INHIBIT стробирует такт. Здесь он подключён к GND, и важен только CLOCK.

НожкаИмяНаправлениеФункцияВ этой схеме
1Q5выход1 в состоянии 5, иначе 0Q5
2Q1выход1 в состоянии 1, иначе 0Q1
3Q0выход1 в состоянии 0, иначе 0Q0
4Q2выход1 в состоянии 2, иначе 0Q2
5Q6выход1 в состоянии 6, иначе 0Q6
6Q7выход1 в состоянии 7, иначе 0Q7
7Q3выход1 в состоянии 3, иначе 0Q3
8VSSпитаниеземля, 0 ВGND
9Q8выход1 в состоянии 8, иначе 0Q8
10Q4выход1 в состоянии 4, иначе 0Q4
11Q9выход1 в состоянии 9, иначе 0Q9
12CARRY OUTвыход1 в состояниях 0-4, 0 в состояниях 5-9; растёт при переходе счёта с 9 на 0 и может тактировать следующий 4017не используется
13CLOCK INHIBITвход1 останавливает счёт, 0 разрешает. Активный уровень высокийна GND
14CLOCKвходсчёт идёт по растущему фронту, с 0 на 1CLK
15RESETвход1 сразу ставит счёт в 0 (Q0 = 1), не дожидаясь такта. Активный уровень высокийRST
16VDDпитаниепитание, +5 В (допустимо от 3 до 18 В)+5V

4072: два элемента ИЛИ на 4 входа

1J2A3B4C5D6NC7VSS8NC9E10F11G12H13K14VDD4072
CD4072B. Ножки 6 и 8 внутри ни к чему не подключены. · SVG · PNG

Два независимых элемента: ножка 1 = ножки 2 ИЛИ 3 ИЛИ 4 ИЛИ 5, ножка 13 = ножки 9 ИЛИ 10 ИЛИ 11 ИЛИ 12. Здесь первый элемент даёт красный, второй — зелёный, так что заняты все восемь входов.

НожкаИмяНаправлениеФункцияВ этой схеме
1JвыходJ = A ИЛИ B ИЛИ C ИЛИ DRED
2Aвходвход элемента ИЛИ с выходом на ножке 1Q3
3Bвходвход элемента ИЛИ с выходом на ножке 1Q1
4Cвходвход элемента ИЛИ с выходом на ножке 1Q0
5Dвходвход элемента ИЛИ с выходом на ножке 1Q2
6NC—внутри микросхемы ни к чему не подключенане подключена
7VSSпитаниеземля, 0 ВGND
8NC—внутри микросхемы ни к чему не подключенане подключена
9Eвходвход элемента ИЛИ с выходом на ножке 13Q7
10Fвходвход элемента ИЛИ с выходом на ножке 13Q6
11Gвходвход элемента ИЛИ с выходом на ножке 13Q8
12Hвходвход элемента ИЛИ с выходом на ножке 13Q9
13KвыходK = E ИЛИ F ИЛИ G ИЛИ HGREEN
14VDDпитаниепитание, +5 В (допустимо от 3 до 18 В)+5V

4071: четыре элемента ИЛИ на 2 входа

1A2B3J4K5C6D7VSS8E9F10L11M12G13H14VDD4071
CD4071B. Используется только элемент с выходом на ножке 4; у бледных элементов входы подключены к шинам питания. · SVG · PNG

Четыре независимых элемента. Ножка 4 = ножка 5 ИЛИ ножка 6 даёт жёлтый из Q5 и Q4. Остальные три элемента не нужны, но их шесть входов всё равно подключены к +5V или GND, а выходы остаются свободными.

НожкаИмяНаправлениеФункцияВ этой схеме
1Aвходвход элемента ИЛИ с выходом на ножке 3на GND
2Bвходвход элемента ИЛИ с выходом на ножке 3на GND
3JвыходJ = A ИЛИ Bне используется
4KвыходK = C ИЛИ DYELLOW
5Cвходвход элемента ИЛИ с выходом на ножке 4Q5
6Dвходвход элемента ИЛИ с выходом на ножке 4Q4
7VSSпитаниеземля, 0 ВGND
8Eвходвход элемента ИЛИ с выходом на ножке 10на +5V
9Fвходвход элемента ИЛИ с выходом на ножке 10на +5V
10LвыходL = E ИЛИ Fне используется
11MвыходM = G ИЛИ Hне используется
12Gвходвход элемента ИЛИ с выходом на ножке 11на +5V
13Hвходвход элемента ИЛИ с выходом на ножке 11на +5V
14VDDпитаниепитание, +5 В (допустимо от 3 до 18 В)+5V

5Схема 1: триггеры и логические элементы

РЕГИСТР СОСТОЯНИЯ: 5-БИТНЫЙ СЧЁТЧИК ДЖОНСОНАПЕРЕХОДЫДЕШИФРАТОР: 10 ЭЛЕМЕНТОВ ИВЫХОДНАЯ ЛОГИКАDQRТ0rstDQRТ1rstDQRТ2rstDQRТ3rstDQRТ4rstинвертированный последний бит идёт в первыйclkr0r0r1r1r2r2r3r3r4r4q0q1q2q3q4q5q6q7q8q9красныйжёлтыйзелёныйточка: провода соединеныкружок: инверсный входrstна R каждого триггера
Вся схема из триггеров и логических элементов, без микросхем. Именно её описывает traffic_light.sv. · SVG · PNG

Читайте её как конечный автомат:

Первые три блока спрятаны внутри 4017; на макетной плате вы собираете только четвёртый.

6Схема 2: соединения между микросхемами

1234567891011121314407112345678910111213141516Q5Q1Q0Q2Q6Q7Q3VSSQ8Q4Q9COINHCLKRSTVDD4017десятичный счётчик12345678910111213144072Q3Q1Q0Q2Q7Q6Q8Q9Q5Q4СБРОСГЕНЕРАТОРRGY+5VGND
Та же схема из микросхем, ножка за ножкой. Каждая микросхема нарисована сверху, выемкой влево; номера ножек стоят рядом с ножками. Цвета проводов те же, что на макетной плате. Подписанные выводы идут к модулям: кнопка сброса и генератор — к 4017, три лампы светофора — к выходам ИЛИ. · SVG · PNG

Все соединения схемы, цепь за цепью. По этому же списку написан traffic_light_chips.sv из раздела 10.

ЦепьОткудаКудаПровод
CLKгенератор 555, OUT4017 14 (CLK)M-F
RSTкнопка сброса, 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)светофор, RM-F
YELLOW4071 4 (K)светофор, YM-F
GREEN4072 13 (K)светофор, GM-F
+5V+5V4071 14 (VDD), 4017 16 (VDD), 4072 14 (VDD); неиспользуемые входы: 4071 13 (H), 4071 12 (G), 4071 9 (F), 4071 8 (E)
GNDGND4071 7 (VSS), 4017 8 (VSS), 4072 7 (VSS); неиспользуемые входы: 4071 1 (A), 4071 2 (B), 4017 13 (INH)
не подключены4017 12 (CO), 4071 3 (J), 4071 10 (L), 4071 11 (M), 4072 6 (NC), 4072 8 (NC)

7Схема 3: макетная плата

−+−+1234567891011121314151617181920212223242526272829303132333435363738394041424344abcdefghij4071401740725V5VБЛОК ПИТАНИЯ · 5VUSB-DC 9VQ3Q1Q0Q2Q7Q6Q8Q9Q5Q4GREEN+OUT−СБРОСкнопка−OUT+555ГЕНЕРАТОР 555~1 ГцGNDGYRСВЕТОФОР
Каждый провод в своём отверстии. Номера столбцов напечатаны сверху; ряды a–e над канавкой, f–j под ней. Кнопка сброса и генератор висят над платой, светофор под ней, все на перемычках папа-мама. Справа плата продолжается до столбца 63. · SVG · PNG
CLKRSTQ0Q1Q2Q3Q4Q5Q6Q7Q8Q9REDYELLOWGREEN+5VGND

Сборка по шагам

  1. Сначала пройдите первые шаги: как макетная плата соединена внутри, блок питания с обеими перемычками на 5 В, как микросхема встаёт поперёк канавки.
  2. Вставьте микросхемы поперёк канавки, выемкой влево: 4071 ножкой 1 в отверстие 12f, 4017 ножкой 1 в 22f, 4072 ножкой 1 в 33f. Проверьте надписи на корпусах.
  3. Питание: красный провод от ножки 14 или 16 на верхнюю шину + и чёрный от ножки 7 или 8 на нижнюю шину −, для каждой микросхемы. Потом провода неиспользуемых входов. Проследите, чтобы ни один провод не касался + и − одновременно.
  4. Одиннадцать сигнальных проводов, в порядке списка ниже. Отмечайте каждый провод, когда вставите его.
  5. Модули, при выключенном питании: кнопку сброса и генератор над платой, светофор под ней. У светофора первым подключите GND.
  6. Включите питание и потенциометром генератора установите частоту около 1 Гц.

Список проводов

·

#ЦепьОткудаКуда
Питание микросхем (красные к +, чёрные к −) · 6 проводов
1+5V12a (4071, ножка 14, VDD)верхняя шина +, столбик 12
2GND18j (4071, ножка 7, VSS)нижняя шина −, столбик 18
3+5V22a (4017, ножка 16, VDD)верхняя шина +, столбик 22
4GND29j (4017, ножка 8, VSS)нижняя шина −, столбик 29
5+5V33a (4072, ножка 14, VDD)верхняя шина +, столбик 33
6GND39j (4072, ножка 7, VSS)нижняя шина −, столбик 39
Неиспользуемые входы: входы КМОП нельзя оставлять висящими · 7 проводов
7GND12j (4071, ножка 1, A)нижняя шина −, столбик 12
8GND13j (4071, ножка 2, B)нижняя шина −, столбик 13
9+5V13a (4071, ножка 13, H)верхняя шина +, столбик 13
10+5V14a (4071, ножка 12, G)верхняя шина +, столбик 14
11+5V17a (4071, ножка 9, F)верхняя шина +, столбик 17
12+5V18a (4071, ножка 8, E)верхняя шина +, столбик 18
13GND25a (4017, ножка 13, INH)верхняя шина −, столбик 25
Сигнальные провода · 11 проводов
14Q328g (4017, ножка 7, Q3)34g (4072, ножка 2, A)
15Q123h (4017, ножка 2, Q1)35h (4072, ножка 3, B)
16Q024i (4017, ножка 3, Q0)36i (4072, ножка 4, C)
17Q225j (4017, ножка 4, Q2)37j (4072, ножка 5, D)
18Q727j (4017, ножка 6, Q7)38c (4072, ножка 9, E)
19Q626g (4017, ножка 5, Q6)37d (4072, ножка 10, F)
20Q829c (4017, ножка 9, Q8)36c (4072, ножка 11, G)
21Q927d (4017, ножка 11, Q9)35d (4072, ножка 12, H)
22Q522i (4017, ножка 1, Q5)16i (4071, ножка 5, C)
23Q428c (4017, ножка 10, Q4)17g (4071, ножка 6, D)
24GREEN34b (4072, ножка 13, K)11g
Кнопка сброса и генератор над платой (перемычки папа-мама) · 6 проводов
25+5Vкнопка сброса, +верхняя шина +, столбик 19
26RSTкнопка сброса, OUT23a (4017, ножка 15, RST)
27GNDкнопка сброса, −верхняя шина −, столбик 21
28+5Vгенератор, +верхняя шина +, столбик 28
29CLKгенератор, OUT24a (4017, ножка 14, CLK)
30GNDгенератор, −верхняя шина −, столбик 26
Модуль светофора под платой (перемычки папа-мама) · 4 провода
31GREENсветофор, G11j
32YELLOWсветофор, Y15j (4071, ножка 4, K)
33REDсветофор, R33j (4072, ножка 1, J)
34GNDсветофор, GNDнижняя шина −, столбик 9

Проверка

Если не работает: лампа, которая никогда не загорается, — обычно провод, сдвинутый на одно отверстие; лампа, которая горит всегда, — вход ИЛИ, по ошибке подключённый к +5V. Две лампы сразу — замкнуты два выхода. Если свет остановился, проверьте ножку 13 микросхемы 4017 (должна быть на GND) и ножку 15 (GND, пока кнопка не нажата). Если микросхемы греются, сразу выключите питание и ищите замыкание между + и −.

8SystemVerilog: общая схема

traffic_light.sv описывает схему 1: регистр Джонсона — это блок always_ff, дешифратор — десять assign с И, выходная логика — три с ИЛИ. Код синтезируемый: маршрут для ПЛИС или ASIC превратит его в пять триггеров и несколько элементов. Выход q показывает состояние, как десять выходов 4017, чтобы его видели тестбенч и платы.

traffic_light.svфайл
// 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] } — это конкатенация: младшие четыре бита сдвигаются на разряд вверх, а снизу приходит инвертированный старший бит. Эта строка и есть сдвиговый регистр со своим элементом НЕ.

9Модели микросхем на SystemVerilog

По модулю на микросхему, порты названы как в даташите, номер ножки каждого порта — в комментарии. Все три модели синтезируемые. Выводов питания в моделях нет: в SystemVerilog у провода просто есть значение 0 или 1.

cd4017.svфайл
// 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

В модели 4017 есть деталь, которой вы, возможно, не ждёте: gated_clock. Настоящая микросхема считает по растущему фронту CLOCK, пока CLOCK INHIBIT в нуле, и ещё по падающему фронту CLOCK INHIBIT, пока CLOCK в единице; элемент И в цепи такта делает ровно это. В ПЛИС такт не должен проходить через логику, поэтому держите clock_inhibit в нуле: синтез тогда уберёт этот элемент. Настоящая микросхема ещё и сама выходит на круг из любого состояния после включения питания; модели нужен сброс.

cd4071.svфайл
// 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.svфайл
// 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: схема из микросхем

traffic_light_chips.sv соединяет модели микросхем точно так же, как соединены микросхемы на схеме 2 и на макетной плате, и у него те же порты, что у traffic_light.sv. Неиспользуемые входы подключены к vdd или gnd, как на плате, а неиспользуемые выходы — к проводам с именами unused_…. Файл генерируется из списка проводов макетной платы после проверки этого списка на уровне ножек, поэтому код, рисунки и список проводов не могут разойтись.

traffic_light_chips.svфайл
// 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

11Тестбенч и скрипты для симуляции

tb.sv запускает обе схемы рядом, от одного такта и одного сброса, и сравнивает их с эталонной моделью: номером состояния от 0 до 9 и цветом, который ему соответствует. Проверка идёт на каждом падающем фронте такта, через полпериода после смены состояния; отдельно проверяется асинхронный сброс посреди состояния. В конце тестбенч считает, сколько тактов горит каждый цвет после сброса, и печатает PASS или FAIL.

tb.sv192 lines · показать кодфайл
// 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

Каждый скрипт компилирует шесть файлов, запускает симуляцию, пишет журнал и временную диаграмму tb.vcd и завершается с ошибкой, если тест не прошёл. Запускать можно из любого каталога: ./run_icarus.bash, ./run_verilator.bash или ./run_questa.bash; ./clean.bash удаляет то, что они создают. Диаграмму открывают в GTKWave или Surfer.

run_icarus.bash22 lines · показать кодфайл
#!/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 · показать кодфайл
#!/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 · показать кодфайл
#!/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

Проверено в Icarus Verilog 12.0 и 14.0 (разрабатываемая версия), в Verilator 5.053 с -Wall без предупреждений и в Questa Altera Starter FPGA Edition 2025.2 из Quartus Prime Lite 25.1. Код использует только то, что поддерживает Icarus Verilog 13. Вот вывод 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)

Чтобы убедиться, что тестбенч ловит ошибки, сломайте что-нибудь и запустите снова: подключите ножку 2 микросхемы 4072 к q [4] вместо q [3] в traffic_light_chips.sv или поменяйте одну строку дешифратора в traffic_light.sv. Скрипт сообщит FAIL, а в распечатке появятся строки с WRONG.

12На плате ПЛИС: lab_top в BGM

Репозиторий Basics-Graphics-Music (BGM) запускает одни и те же лабораторные работы на десятках плат ПЛИС. В каждой работе есть модуль lab_top с одинаковыми на всех платах портами: такт, медленный такт slow_clk на 1 Гц, сброс, кнопки, светодиоды, семисегментный индикатор.

  1. Скопируйте работу, например labs/1_basics/1_06_binary_counter, в новый каталог, скажем labs/1_basics/1_11_traffic_light.
  2. Замените в нём lab_top.sv на bgm_lab/lab_top.sv из этого упражнения и добавьте traffic_light.sv. Скрипты BGM компилируют все файлы .sv каталога, кроме tb.sv.
  3. Запустите ./03_synthesize_for_fpga.bash и ./04_configure_fpga.bash; при первом запуске скрипт спросит, какая у вас плата.

Светофор работает от slow_clk, который каждая плата BGM делает на 1 Гц, поэтому он шагает раз в секунду, как на макетной плате. BGM делает его в peripherals/slow_clk_gen.sv: счётчик на такте платы переключает триггер, а на ПЛИС AMD (Xilinx) и Intel (Altera) примитив BUFG или global выводит этот сигнал в тактовую сеть ПЛИС, так что он доходит до всех триггеров как настоящий такт. Правый разряд индикатора — маленький светофор: сегмент a (верхний) красный, g (средний) жёлтый, d (нижний) зелёный. Светодиоды показывают состояние бегущим огоньком. Чтобы запустить схему из микросхем, добавьте traffic_light_chips.sv и три модели микросхем и поменяйте имя модуля в экземпляре.

bgm_lab/lab_top.sv117 lines · показать кодфайл
// 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

13На плате ПЛИС: hackathon_top в BGM

В варианте BGM для хакатонов используется Tang Nano 9K с экраном 480×272 и платой TM1638 с кнопками, светодиодами и индикатором. У верхнего модуля hackathon_top порты проще, а первая строка файла называет плату, так что скрипты ничего не спрашивают.

  1. Скопируйте каталог, например hackathon/problems/4_binary_counter.
  2. Замените в нём hackathon_top.sv на bgm_hackathon/hackathon_top.sv и добавьте traffic_light.sv.
  3. Запустите ./03_synthesize_for_fpga.bash и ./04_configure_fpga.bash.

На экране светофор: три круглые лампы в тёмном корпусе, яркие, когда горят, и тусклые, когда нет. Правый разряд индикатора показывает те же три сегмента, что в lab_top, а восемь светодиодов — состояния 0–7. Порты цвета экрана уже называются red и green, поэтому сигналы светофора здесь называются light_red, light_yellow и light_green.

bgm_hackathon/hackathon_top.sv112 lines · показать кодфайл
// 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

14На кристалле: Tiny Tapeout

Tiny Tapeout выпускает настоящие микросхемы из проектов учащихся. Каждый проект — модуль с портами, как ниже; на демонстрационной плате ui_in идёт от DIP-переключателей, а uo_out управляет семисегментным индикатором.

Демонстрационная плата сама делает такт проекта и может задать его от 1 Гц до 66,5 МГц (см. раздел Clock в документации Tiny Tapeout, на английском). Задайте 1 Гц, и project.v подаёт clk прямо на светофор, так же как модуль на 555 тактирует 4017 на макетной плате. Индикатор показывает свет на сегментах a, g и d.

tiny_tapeout/project.v54 lines · показать кодфайл
/*
 * 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

В info.yaml задайте такт 1 Гц, перечислите оба файла и укажите имя модуля как верхний модуль:

project:
  clock_hz:   1                 # демонстрационная плата делает такт 1 Гц
  top_module: "tt_um_example"   # переименуйте в tt_um_<ваш логин на GitHub>_traffic_light
  source_files:
    - "project.v"
    - "traffic_light.sv"
На кристалле такт счётчиком не делят. BGM делает slow_clk счётчиком, но на ПЛИС AMD и Intel выводит этот сигнал в тактовую сеть примитивом BUFG или global (раздел 12); так поступают и в некоторых промышленных проектах на ПЛИС. В ASIC бит счётчика в роли такта — плохая практика: это второй такт со своим тактовым деревом, его фронты отстают от фронтов clk на задержку, которую никто не контролирует, о нём нужно отдельно сообщить средствам временного анализа, а каждый сигнал между двумя тактами пересекает границу тактовых доменов. Здесь плата сама даёт 1 Гц, так что делитель не нужен. Если на кристалле нужен медленный шаг от быстрого такта, оставьте один такт и добавьте разрешение такта (задача в разделе 15).

15Что дальше

Задания для учащихся

Сокращение, которое прячет микросхема

Если регистр Джонсона виден, дешифратор и элементы ИЛИ сокращаются до трёх элементов на два входа: красный — это состояния 0–3, то есть ровно ~ r [3] & ~ r [4]; жёлтый — r [3] & r [0]; зелёный — r [4] & ~ r [0]. Проверьте по таблице состояний из раздела 2. У 4017 так не выйдет: биты счётчика Джонсона спрятаны внутри микросхемы. А в ПЛИС это бесплатная оптимизация, и средства синтеза находят такое сами.

Светофор на 555 и 4017 собирают уже несколько десятилетий, чаще всего на диодах. Несколько вариантов для сравнения (по-английски):

Файлы упражнения

Источники

Схема Тимофея Никифорова описана в статье на Хабре о курсе электроники (занятие 1.7) и в «Teaching children electronics is due for a major redesign», рисунок 15. Микросхемы 4017, 4071 и 4072 описаны в даташитах Texas Instruments на CD4017B, CD4071B и CD4072B.