В данном топике хочу рассказать о том, как на ПЛИС можно реализовать часы. Кому-то покажется это странным, ненужным — но надо же с чего-то начинать, поэтому, этот топик будет полезным для начинающих, которые светодиодами помигали и хотят что-нить поинтереснее.

Значит, что мы имеем:
1) Отладочную плату Terasic DE0-Nano с Cyclone IV на борту.

2) Макетку с распаянным на ней семисегментным индикатором, на который мы будем выводить часы и минуты/
По этим двум пунктам полная свобода выбора практически любая ПЛИС подойдет, да и макетку с индикатором каждый сделает так, как сделает.
Теперь о проекте.
Будем идти по порядку. Перво-наперво зададимся вопросом: что такое часы? Часы — это прибор, который отсчитывает время. Для того, чтобы его отсчитывать, нам нужен точный источник. На нашей плате есть генератор тактового сигнала 50 МГц. Многовато, поэт��му нам нужно его разделить на 50 000 000, чтобы получить частоту 1 Гц(один «тик» в секунду). Как поделить? Я сделал это в два этапа: используя PLL, поделил на 50 и используя счетчик, поделил на миллион.
Что такое PLL? Грубо говоря, это такая штука, которая может изменять тактовый сигнал: умножать, делить, сдвигать по фазе, менять скважность. В Cyclone IV их 4 штуки, так почему бы не воспользоваться одной…
Для этого открываем MegaWizard Plugin Manager и используем ALT_PLL из раздела IO:
1) Устанавливаем входную частоту 50 МГц:

2) Отключаем ненужные входы:

3)Три раза жмем Next и попадаем в окно выбора параметров сигнала, где задаем коэффициент деления 50:

Дальше можно смело нажимать Finish, сохранять и вставлять в проект.
Вот такую красоту мы получили:

Слева вход 50 МГц, справа 1 МГц.
Теперь нам нужно поделить еще на миллион — для этого используем счетчик по модулю 1000000. Модуль счета — это количество возможных состояний счетчика.
Создаем его с помощью того же MegaWizard, используя LPM_Counter.
Задаем разрядность — 20 бит, и выбираем modulus, with a count modulus of 1000000.
Получаем вот что:

Теперь у нас аккуратненькие 1 Гц, которые можно получить с старшего (19) разряда.
Дальше нам необходимо реализовать каскад из 3-х счетчиков: два по модулю 60 (минуты и секунды), и один по модулю 24(очевидно, часы).
Секундный счетчик — с асинхронным сбросом, который происходит при нажатии любой кнопки(установка часов/минут):

Как видно, использован выход Carry Out (перенос при переполнении в следующий счетчик), а секунды выводятся на светодиоды.
Следующие 2 счетчика строятся аналогично, за одним исключением: для установки времени необходимо подавать тактовый сигнал побыстрее 1 Гц, для того, чтобы ��е уснуть при настройке. :)
Для этого используем мультиплексоры, управляемые кнопками:

Верхний мультиплексор используется для выбора, какую частоту подавать на вход счетчика, а нижний — выбирает, что подавать на вход cin: перенос из cout секундного счетчика, или просто уровень лог. «1».
Суть входа cin в том, что счетчик считает только при наличии на этом входе уровня логической единицы. То есть, когда счетчик секунд переполняется, он поднимает cout в единицу. Эта «1» поступает на вход cin следующего счетчика и он отчитывает одну минуту, и так далее.
Таким образом, при отпущенной кнопке key[0] минутный счетчик считает 1 раз в минуту(что логично), а при нажатой — где-то 4 раза в секунду.
Аналогично для часового счетчика:

Верхний мультиплексор заведует переносом, а нижний частотой, за исключением одного момента: при нажатой кнопке key[0] тактовые сигналы на вод счетчика не поступают(это сделано для того, чтобы он часы не сбивались при настройке минут). Это сделано за счет элемента AND, на вход которого подается тактовый сигнал и key[0]. При отпущенной кнопке на key[0] высокий уровень и q[19] проходит через него, при нажатой — уровень «0» и на выходе тоже «0». В принципе, можно было бы включить у счетчика вход «count enable» и отключать счетчик через него — в данной задаче это непринципиально.
Теперь нам надо выводить данные с счетчиков. Для этого нам необходимо сделать преобразователь из двоичного кода в двоично-десятичный. Это такое представление десятичного числа в двоичном коде, когда каждая десятичная цифра задается отдельным 4мя двоичными разрядами.
Пример:
738 = 0111 0011 1000.
Для этого преобразования воспользуемся алгоритмом double-dabble.
Суть алгоритма:
1) Сдвинуть двоичное число влево на один разряд.
2) Если 4 сдвига — число BCD в колонках «десятки» и «единицы». Конец алгоритма
3) Если в любой колонке число больше 4 — прибавить 3.
Перейти к п1.
Вот иллюстрация:

А вот то, что нам нужно реализовать, чтобы это преобразование выполнялось без задержек:
s16.radikal.ru/i191/1107/a4/59618e3101ca.png
Каждый модуль С прибавляет к входным данным 3, если на входе число, большее, чем 4.
Вот он сам и таблица истинности его работы:

Для реализации всего этого хозяйства пишем вот два таких модуля на verilog:
Сохраняем и вставляем в проект в количестве двух штук:

Один будет для часов, другой — для минут.
Теперь необходимо сделать лирическое отступление касательно нашего индикатора.
Я использовал Lite-On ltc-4727js.
Согласно даташиту, у него сегменты с общим катодом для каждой цифры + входы анодов сегментов каждой цифры объединены. Это значит, что мы сможем одновременно зажигать только 1 цифру. Не беда, будем зажигать их по-очереди, только переключать будем очень быстро. Так быстро, что даже Чак Норрис не заметит мерцания ;-)
Самые интересные части из даташита:

и

Как я это все подключил?
А вот используя такие пины:

С сегментами, думаю, все ясно, а вот что такое загадочное digit[4..0]… Всё просто — digit[4..1] —
это наши цифры, а digit[0] — это вспомогательные сегменты(двоеточие между 2 и 3 цифрами). Скажу вам, самое трудное — это правильно подключить все и правильно поставить соответствие между выходами ПЛИС и нашими пинами!
Разберем теперь механизм вывода на индикатор.
Занимается этим вот такая монструозная конструкция:

Слева у нас конверторы bin-to-BCD. В центре внимания — мультиплексор на 5 входов шириной 4 бита. Первые 4 входа — цифры, пятый вход — для зажигания точек(которые мигают раз в секунду). Внизу виднеется схемка, которая на этот секундный вход подает 1111 или 1110. О том, почему именно так — немного дальше. В зависимости от того, какая комбинация приходит на управляющий вход мультиплексора, он выводит нужный разряд на дешифратор(устройство которого рассмотрим чуть позже). Выше него — девайс, который выбирает, на какой катод подавать «0», чтобы загорался нужный сегмент. Вот его внутренности:
Обратите внимание на запись вида 5'b0zzzz. Здесь 5'b означает, что мы задаем 5 бит в двоичном виде, 0 — уровень нуля, z — высокоомное состояние(ток в пин не течет). А почему именно 0? Да потому, что у нас общий катод на цифре, ток течет от анода к катоду, или от 1 к 0. Вот и задаем на катоде 0, а на аноде 1.
Теперь устройство дешифратора:
assign {segA, segB, segC, segD, segE, segF, segG, segDP} = SevenSeg;
endmodule
Тут в зависимости от того, какая цифра нам нужна, выбирается, какие сегменты зажигать.
Интересуют два момента — default и число 1111. Когда у нас BCD принимает значение, отличное от заданных(1110, о котором говорилось ранее) — то все сегменты выключены. Когда у нас приходит 1111 — то зажигаются сегменты A и B. Как можно видеть из даташита, они же подключены к точкам L1 и L2.
Вот, собственно, и все.
Собираем все водну кучу один проект, назначаем пины, компилируем(ага, используется менее 1% нашего могучего камушка) и заливаем в ПЛИС.
Вот проект:
ifolder.ru/24865983
Необходимо выбрать вашу ПЛИС, и переназначить пины. Правда, я использовал PLL… Поэтому при переносе проекта на другую ПЛИС необходимо будет вашу входную частоту(не факт, что у вас там будет 50 МГЦ) поделить самостоятельно.
Вот видео:
www.youtube.com/watch?v=iUoiOO8wYls
Источники:
1) Даташит на индикатор:
pdf.eicom.ru/datasheets/lite-on_pdfs/ltc-4727js/ltc-4727js.pdf
2) Перевод из BIN в BCD:
www.johnloomis.org/ece314/notes/devices/binary_to_BCD/bin_to_bcd.html
www.ece.msstate.edu/courses/ece4743/fall2007/Shift_add_3.pdf
3) Вывод на сегменты:
www2.engr.arizona.edu/~rlysecky/courses/ece274-07f/labs/lab4.pdf
we.easyelectronics.ru/Shematech/dinamicheskaya-indikaciya_2.html
www.fpga4fun.com/Opto4.html
4)Дешифратор:
www.fpga4fun.com/Opto3.html
Спасибо за внимание, надеюсь, кому-нибудь пригодится!
UPD
Спасибо за комментарии всем!
Я не знаю, как оно оказалось в этом блоге. Я хотел перепилить, залить картинки норм, переделать кое-чего… Но я сегодня вообще на Хабр не заходил!
Есть идея о том, чтобы сделать получение времени извне, например по NTP.
Про хостинг — мне в личку посоветовали, перелью туда.
Это мой первый топик, до этого опыта публикаций не было, картинки залил на первый вспомнившийся хостинг.
Я только сегодня оказался в родном городе и с нормальным интернетом. Завтра перепилю с учетом пожеланий!

Значит, что мы имеем:
1) Отладочную плату Terasic DE0-Nano с Cyclone IV на борту.

2) Макетку с распаянным на ней семисегментным индикатором, на который мы будем выводить часы и минуты/
По этим двум пунктам полная свобода выбора практически любая ПЛИС подойдет, да и макетку с индикатором каждый сделает так, как сделает.
Теперь о проекте.
Будем идти по порядку. Перво-наперво зададимся вопросом: что такое часы? Часы — это прибор, который отсчитывает время. Для того, чтобы его отсчитывать, нам нужен точный источник. На нашей плате есть генератор тактового сигнала 50 МГц. Многовато, поэт��му нам нужно его разделить на 50 000 000, чтобы получить частоту 1 Гц(один «тик» в секунду). Как поделить? Я сделал это в два этапа: используя PLL, поделил на 50 и используя счетчик, поделил на миллион.
Что такое PLL? Грубо говоря, это такая штука, которая может изменять тактовый сигнал: умножать, делить, сдвигать по фазе, менять скважность. В Cyclone IV их 4 штуки, так почему бы не воспользоваться одной…
Для этого открываем MegaWizard Plugin Manager и используем ALT_PLL из раздела IO:
1) Устанавливаем входную частоту 50 МГц:

2) Отключаем ненужные входы:

3)Три раза жмем Next и попадаем в окно выбора параметров сигнала, где задаем коэффициент деления 50:

Дальше можно смело нажимать Finish, сохранять и вставлять в проект.
Вот такую красоту мы получили:

Слева вход 50 МГц, справа 1 МГц.
Теперь нам нужно поделить еще на миллион — для этого используем счетчик по модулю 1000000. Модуль счета — это количество возможных состояний счетчика.
Создаем его с помощью того же MegaWizard, используя LPM_Counter.
Задаем разрядность — 20 бит, и выбираем modulus, with a count modulus of 1000000.
Получаем вот что:

Теперь у нас аккуратненькие 1 Гц, которые можно получить с старшего (19) разряда.
Дальше нам необходимо реализовать каскад из 3-х счетчиков: два по модулю 60 (минуты и секунды), и один по модулю 24(очевидно, часы).
Секундный счетчик — с асинхронным сбросом, который происходит при нажатии любой кнопки(установка часов/минут):

Как видно, использован выход Carry Out (перенос при переполнении в следующий счетчик), а секунды выводятся на светодиоды.
Следующие 2 счетчика строятся аналогично, за одним исключением: для установки времени необходимо подавать тактовый сигнал побыстрее 1 Гц, для того, чтобы ��е уснуть при настройке. :)
Для этого используем мультиплексоры, управляемые кнопками:

Верхний мультиплексор используется для выбора, какую частоту подавать на вход счетчика, а нижний — выбирает, что подавать на вход cin: перенос из cout секундного счетчика, или просто уровень лог. «1».
Суть входа cin в том, что счетчик считает только при наличии на этом входе уровня логической единицы. То есть, когда счетчик секунд переполняется, он поднимает cout в единицу. Эта «1» поступает на вход cin следующего счетчика и он отчитывает одну минуту, и так далее.
Таким образом, при отпущенной кнопке key[0] минутный счетчик считает 1 раз в минуту(что логично), а при нажатой — где-то 4 раза в секунду.
Аналогично для часового счетчика:

Верхний мультиплексор заведует переносом, а нижний частотой, за исключением одного момента: при нажатой кнопке key[0] тактовые сигналы на вод счетчика не поступают(это сделано для того, чтобы он часы не сбивались при настройке минут). Это сделано за счет элемента AND, на вход которого подается тактовый сигнал и key[0]. При отпущенной кнопке на key[0] высокий уровень и q[19] проходит через него, при нажатой — уровень «0» и на выходе тоже «0». В принципе, можно было бы включить у счетчика вход «count enable» и отключать счетчик через него — в данной задаче это непринципиально.
Теперь нам надо выводить данные с счетчиков. Для этого нам необходимо сделать преобразователь из двоичного кода в двоично-десятичный. Это такое представление десятичного числа в двоичном коде, когда каждая десятичная цифра задается отдельным 4мя двоичными разрядами.
Пример:
738 = 0111 0011 1000.
Для этого преобразования воспользуемся алгоритмом double-dabble.
Суть алгоритма:
1) Сдвинуть двоичное число влево на один разряд.
2) Если 4 сдвига — число BCD в колонках «десятки» и «единицы». Конец алгоритма
3) Если в любой колонке число больше 4 — прибавить 3.
Перейти к п1.
Вот иллюстрация:

А вот то, что нам нужно реализовать, чтобы это преобразование выполнялось без задержек:
s16.radikal.ru/i191/1107/a4/59618e3101ca.png
Каждый модуль С прибавляет к входным данным 3, если на входе число, большее, чем 4.
Вот он сам и таблица истинности его работы:

Для реализации всего этого хозяйства пишем вот два таких модуля на verilog:
module add3(in,out);
input [3:0] in;
output [3:0] out;
reg [3:0] out;
always @ (in)
case (in)
4'b0000: out <= 4'b0000;
4'b0001: out <= 4'b0001;
4'b0010: out <= 4'b0010;
4'b0011: out <= 4'b0011;
4'b0100: out <= 4'b0100;
4'b0101: out <= 4'b1000;
4'b0110: out <= 4'b1001;
4'b0111: out <= 4'b1010;
4'b1000: out <= 4'b1011;
4'b1001: out <= 4'b1100;
default: out <= 4'b0000;
endcase
endmodule
module binary_to_BCD(A,ONES,TENS);
input [5:0] A;
output [3:0] ONES, TENS;
wire [3:0] c1,c2,c3;
wire [3:0] d1,d2,d3;
assign d1 = {1'b0,A[5:3]};
assign d2 = {c1[2:0],A[2]};
assign d3 = {c2[2:0],A[1]};
add3 m1(d1,c1);
add3 m2(d2,c2);
add3 m3(d3,c3);
assign ONES = {c3[2:0],A[0]};
assign TENS = {1'b0,c1[3],c2[3],c3[3]};
endmodule
Сохраняем и вставляем в проект в количестве двух штук:

Один будет для часов, другой — для минут.
Теперь необходимо сделать лирическое отступление касательно нашего индикатора.
Я использовал Lite-On ltc-4727js.
Согласно даташиту, у него сегменты с общим катодом для каждой цифры + входы анодов сегментов каждой цифры объединены. Это значит, что мы сможем одновременно зажигать только 1 цифру. Не беда, будем зажигать их по-очереди, только переключать будем очень быстро. Так быстро, что даже Чак Норрис не заметит мерцания ;-)
Самые интересные части из даташита:

и

Как я это все подключил?
А вот используя такие пины:

С сегментами, думаю, все ясно, а вот что такое загадочное digit[4..0]… Всё просто — digit[4..1] —
это наши цифры, а digit[0] — это вспомогательные сегменты(двоеточие между 2 и 3 цифрами). Скажу вам, самое трудное — это правильно подключить все и правильно поставить соответствие между выходами ПЛИС и нашими пинами!
Разберем теперь механизм вывода на индикатор.
Занимается этим вот такая монструозная конструкция:

Слева у нас конверторы bin-to-BCD. В центре внимания — мультиплексор на 5 входов шириной 4 бита. Первые 4 входа — цифры, пятый вход — для зажигания точек(которые мигают раз в секунду). Внизу виднеется схемка, которая на этот секундный вход подает 1111 или 1110. О том, почему именно так — немного дальше. В зависимости от того, какая комбинация приходит на управляющий вход мультиплексора, он выводит нужный разряд на дешифратор(устройство которого рассмотрим чуть позже). Выше него — девайс, который выбирает, на какой катод подавать «0», чтобы загорался нужный сегмент. Вот его внутренности:
module segment_select (in,out,sel);
input in;
output reg [4:0] out;
output reg [2:0] sel;
always @ (posedge in)
if (sel == 4)
sel = 0;
else sel = sel + 1;
always @(*)
case (sel)
0: out <= 5'b0zzzz;
1: out <= 5'bz0zzz;
2: out <= 5'bzz0zz;
3: out <= 5'bzzz0z;
4: out <= 5'bzzzz0;
default: out <= 5'bzzzzz;
endcase
endmodule
Обратите внимание на запись вида 5'b0zzzz. Здесь 5'b означает, что мы задаем 5 бит в двоичном виде, 0 — уровень нуля, z — высокоомное состояние(ток в пин не течет). А почему именно 0? Да потому, что у нас общий катод на цифре, ток течет от анода к катоду, или от 1 к 0. Вот и задаем на катоде 0, а на аноде 1.
Теперь устройство дешифратора:
module decoder_7seg (BCD, segA, segB, segC, segD, segE, segF, segG, segDP);
input [3:0] BCD;
output segA, segB, segC, segD, segE, segF, segG, segDP;
reg [7:0] SevenSeg;
always @(BCD)
case(BCD)
4'h0: SevenSeg = 8'b11111100;
4'h1: SevenSeg = 8'b01100000;
4'h2: SevenSeg = 8'b11011010;
4'h3: SevenSeg = 8'b11110010;
4'h4: SevenSeg = 8'b01100110;
4'h5: SevenSeg = 8'b10110110;
4'h6: SevenSeg = 8'b10111110;
4'h7: SevenSeg = 8'b11100000;
4'h8: SevenSeg = 8'b11111110;
4'h9: SevenSeg = 8'b11110110;
4'b1111: SevenSeg = 8'b11000000;
default: SevenSeg = 8'b00000000;
endcaseassign {segA, segB, segC, segD, segE, segF, segG, segDP} = SevenSeg;
endmodule
Тут в зависимости от того, какая цифра нам нужна, выбирается, какие сегменты зажигать.
Интересуют два момента — default и число 1111. Когда у нас BCD принимает значение, отличное от заданных(1110, о котором говорилось ранее) — то все сегменты выключены. Когда у нас приходит 1111 — то зажигаются сегменты A и B. Как можно видеть из даташита, они же подключены к точкам L1 и L2.
Вот, собственно, и все.
Собираем все в
Вот проект:
ifolder.ru/24865983
Необходимо выбрать вашу ПЛИС, и переназначить пины. Правда, я использовал PLL… Поэтому при переносе проекта на другую ПЛИС необходимо будет вашу входную частоту(не факт, что у вас там будет 50 МГЦ) поделить самостоятельно.
Вот видео:
www.youtube.com/watch?v=iUoiOO8wYls
Источники:
1) Даташит на индикатор:
pdf.eicom.ru/datasheets/lite-on_pdfs/ltc-4727js/ltc-4727js.pdf
2) Перевод из BIN в BCD:
www.johnloomis.org/ece314/notes/devices/binary_to_BCD/bin_to_bcd.html
www.ece.msstate.edu/courses/ece4743/fall2007/Shift_add_3.pdf
3) Вывод на сегменты:
www2.engr.arizona.edu/~rlysecky/courses/ece274-07f/labs/lab4.pdf
we.easyelectronics.ru/Shematech/dinamicheskaya-indikaciya_2.html
www.fpga4fun.com/Opto4.html
4)Дешифратор:
www.fpga4fun.com/Opto3.html
Спасибо за внимание, надеюсь, кому-нибудь пригодится!
UPD
Спасибо за комментарии всем!
Я не знаю, как оно оказалось в этом блоге. Я хотел перепилить, залить картинки норм, переделать кое-чего… Но я сегодня вообще на Хабр не заходил!
Есть идея о том, чтобы сделать получение времени извне, например по NTP.
Про хостинг — мне в личку посоветовали, перелью туда.
Это мой первый топик, до этого опыта публикаций не было, картинки залил на первый вспомнившийся хостинг.
Я только сегодня оказался в родном городе и с нормальным интернетом. Завтра перепилю с учетом пожеланий!
