Pull to refresh

Делаем таймер или первый проект на ПЛИС

DIY
Начну свою первую статью с того, что сообщу: в предмете статьи я сам новичок, но выбрал именно такую тему. Объясню почему. Читаю хабр уже достаточно долго и мне всегда были интересны топики тех, кто сам в настоящий момент изучает то, о чем повествует. Такие статьи всегда понятны, всегда находят свою аудиторию и всегда предают читающему интерес и энтузиазм автора, который у новичков в любой области обычно зашкаливает!

Так вот, недавно я заинтересовался темой ПЛИС, столкнувшись с ними на учебе, прочитал много всяких доков и заказал себе Starter Kit для экспериментов. Теперь, получив полигон для экспериментов по почте и проведя первый эксперимент, я готов поделиться с хабраюзерами. Надеюсь будет интересно.

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

ПЛИС (Программируемая логическая интегральная схема) – один из видов электронных устройств, применяемый для реализации различных логических устройств самой разной сложности, от двоичных счетчиков, несложных логических схем (замены стандартных интегральных схем — рассыпухе) до специализированных процессоров и нейрочипов.

С назначением разобрались, вопрос – как? Внутри ПЛИС находятся некие базовые элементы, которые соединяются на основе конфигурационной записи. Возможные базовые элементы, вид и место хранения конфигурационной записи зависят от вида ПЛИС и от конкретной модели. В современных ПЛИС выделяют два вида: CPLD и FPGA, уделим им по абзацу.

CPLD (complex programmable logic device — сложные программируемые логические устройства) — ПЛИС, базовыми элементами которой являются макроячейки и простые логические вентили (И(-НЕ)/ИЛИ(-НЕ)). Обычно содержит меньше базовых элементов, чем FPGA, но является более быстродействующей. Также обычно содержит энергонезависимую конфигурационную память прямо на кристалле, но имеет ограниченное число циклов конфигурирования.

FPGA (field-programmable gate array — Программируемая пользователем вентильная матрица) – ПЛИС, которые обычно имеют целый букет видов базовых блоков, это и настраиваемые логические элементы (таблицами истинности) и блоки сложения-умножения (Digital signal processing — DSP) и PLL (Phase-Locked Loop) для деления и умножения частоты и некоторые другие в зависимости от модели. Обычно имеют энергозависимую внутреннюю память и функционал для загрузки конфигурации с внешней энергонезависимой памяти.

Определения этих видов ПЛИС и разницы между ними можно встретить самые различные, как в книгах, так и в интернете. Поэтому не стоит зацикливаться на этой разнице, производители сами классифицируют выпускаемые ими ПЛИС. Если вы не запомнили аббревиатур или не поняли значений некоторых слов – не страшно, главное чтобы появилось общее понятие о ПЛИС, надеюсь я этого добился. А теперь к практике!

Начнем с обсуждения инструментов. Я буду пользоваться стартовым набором разработчика Altera Cyclone II FPGA Starter Board, это готовая плата, на которой установлена FPGA серии Cyclone II – EP2C20F484C7N, а также различная периферия и интерфейсы. В этой статье мы используем светодиоды и семисегментные индикаторы. Мы не будем использовать никаких специальных блоков данной серии FPGA, поэтому при желании вы можете использовать почти любую другую ПЛИС (FPGA и CPLD).



Сильно заинтересовавшиеся могут купить себе один из наборов разработчика или самим собрать устройство, что является достаточно сложной для новичка, но вполне выполнимой задачей. Схемы программаторов и схемы подключения самих ПЛИС легко гуглятся, к тому же, кто ранее занимался любительской прошивкой AVR, может обнаружить у себя подходящий программатор Altera Byte Blaster. В общем, схемную реализацию я предоставляю тебе хабраюзер (В конце статьи схема моей Starter Board). Кто хочет попробовать без денежных затрат и увидеть результат работы – можно использовать встроенный в Quartus II симулятор (в этой статье работа с ним не описана).

Из ПО мы будем использовать Quartus II, free версию которого вы сможете найти на сайте производителя (Altera), как в Windows, так и в Linux вариантах.

И вот мы подошли к практике вплотную! Запускаем наш САПР Quartus II и создаем проект. Первыми шагами визарда указываем имя проекта и его место дислокации, затем пропустим шаг добавления файлов (еще успеем). Закончим создание проекта на этапе выбора устройства, если делаем на железяке – точно знаем имя ПЛИС, его и выбираем. Если просто делаем проект для ПЛИС выберем что-нибудь помощней, например третий циклон. Я же выбираю FPGA которая установлена в моем стартер ките.

1_proj_wizard_ch


Жмем Finish – проект создан. Структура проекта в Quartus – иерархическая, нам необходимо выбрать верхушку иерархии (Top-Level Entity). Мы можем использовать для проектирования схемные файлы и файлы с описанием логики на одном из HDL (Hardware description language – язык описания аппаратуры). Мое мнение – наглядней всего в качестве верхушки иерархии использовать схемный файл с основными блоками логики, а сами блоки реализовывать на HDL. Мнения могут быть разными, пока выбирал, прочитал немало холиварных тем, но остановился пока на такой модели, вы можете сделать свой выбор. В качестве HDL языка проекта я выбрал VHDL ((Very high speed integrated circuits) Hardware Description Language), вы можете использовать любой другой, например Verilog или AHDL, все зависит от ваших предпочтений.

Создаем наш первый файл проекта (File – New..) выбираем Block Diagram/Schematic File. Теперь давайте нарисуем простейшую схему, добавим один Input, один Output и соединим их (в реальной ПЛИС эта схема будет передавать сигнал с одной ножки на другую). Для этого Double Click на пустом месте схемы и в открывшемся диалоге Symbol выбираем необходимый элемент.

2_add_inp_outp


Соединяем просто так, линией. Дадим пинам имена (Double Click по элементам), input назову CLOCK_27[0], а output назову LEDR[0]. Имена выбраны не случайно – CLOCK_27[0] я затем ассоциирую с входом генератора 27Mhz, а LEDR[0] с нулевым красным светодиодом. Сохраняем файл, оставив галочку добавления в проект. Теперь установим полученный файл вершиной иерархии проекта. Для этого окне Project Navigator, во вкладке Files, в контекстном меню нашего файла выбираем Set as Top-Level Entity. Hello, world готов. Компилируем проект (Processing – Start Compilation), если видим Info: Quartus II Full Compilation was successful. – радуемся и считаем, что первый этап пройден.

Теперь разберемся с нашими целями. Наше устройство будет при включении питания начинать отсчет минут и часов. Значит нам понадобится четыре семисегментных индикатора «ЧЧ: ММ». Для отсчета времени нам нужен более-менее точный сигнал 1Hz. Его мы получим путем деления частоты 27Mhz, затем мы будем отсчитывать его на 60 (секунды), потом еще раз на 60 (минуты), а потом на 24 (часы). С последних двух блоков двоичное число минут и часов будет поступать на декодер Bin -> BCD (binary-coded decimal) -> 7seg. Вот, в общем, и все устройство. Сразу оговорюсь, что схема будет асинхронная (Минуты заводятся от секунд, а часы от минут), для простоты и наглядности.

3_block_shem


Итак, создадим наш первый блок — блок деления частоты. Создадим новый файл, как мы уже умеем, только тип файла будет VHDL File. Вставим в него код:

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

entity Div_27Mhz_to_1Hz is
port( clk:in std_logic; clk_out:out std_logic);
end Div_27Mhz_to_1Hz;

architecture div_behavior of Div_27Mhz_to_1Hz is
begin
process(clk)
variable cnt : integer range 0 to 27000000;
begin
if(clk'event and clk = '1')
then

if(cnt >= 13500000)
then
clk_out <= '1';
else
clk_out <= '0';
end if;

if(cnt = 27000000)
then
cnt := 0;
else
cnt := cnt + 1;
end if;

end if;
end process;
end div_behavior;


Пропущу служебные директивы (можно посмотреть в справочнике, ссылки в конце), уделю внимание только логике работы. Вначале мы объявляем сущность, т.е. сам блок. Указываем его входы и выходы, их типы и имена. Тип std_logic в простонародье значит бит. Далее мы описываем внутреннюю архитектуру этого блока. Архитектура состоит из параллельных процессов. Каждый процесс имеет свой список чувствительности, например единственный процесс в примере выше чувствителен к изменениям на входе clk. Для процесса можно объявить переменные, в нашем примере это переменная типа integer range 0 to 27000000. Далее в теле процесса задается элементарная логика: пока не прошла половина периода — пихаем в выход логический ноль, как половина прошла – пихаем единицу, при этом не забываем считать и обнулять счетчик по достижению 27000000. На идеальный код не претендую – пока учусь, буду рад поправкам :)

Сохраняем файл с кодом и создаем символ (File – Create/Update – Create Symbol Files For Current Files), это необходимо для того чтобы вставить данный блок в нашу главную схему. Найти свои символы можно в папке Project в диалоге вставки символа. Теперь пробежимся по остальным блокам менее подробно.

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

-- For CONV_STD_LOGIC_VECTOR:
use ieee.std_logic_arith.all;

entity cnt_0_to_59 is
port( clk:in std_logic; c59:out std_logic; vector:out std_logic_vector(5 downto 0));
end cnt_0_to_59;

architecture cnt_behavior of cnt_0_to_59 is
begin
process(clk)
variable cnt : integer range 0 to 59;
begin
if(clk'event and clk = '1')
then
if(cnt = 59)
then
cnt := 0;
c59 <= '1';
vector <= CONV_STD_LOGIC_VECTOR(cnt, 6);
else
cnt := cnt + 1;
c59 <= '0';
vector <= CONV_STD_LOGIC_VECTOR(cnt, 6);
end if;
end if;
end process;
end cnt_behavior;


Это блок счета от нуля до 59, который мы используем для счета минут и секунд. Из новинок тут тип выхода std_logic_vector(5 downto 0), который определяет группу битов (битовый вектор), а также функция CONV_STD_LOGIC_VECTOR(cnt, 6), которая преобразует переменную в битовый вектор указанной длины.

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

-- For CONV_STD_LOGIC_VECTOR:
use ieee.std_logic_arith.all;

entity cnt_0_to_23 is
port( clk:in std_logic; vector:out std_logic_vector(4 downto 0));
end cnt_0_to_23;

architecture cnt_behavior of cnt_0_to_23 is
begin
process(clk)
variable cnt : integer range 0 to 23;
begin
if(clk'event and clk = '1')
then
if(cnt = 23)
then
cnt := 0;
vector <= CONV_STD_LOGIC_VECTOR(cnt, 5);
else
cnt := cnt + 1;
vector <= CONV_STD_LOGIC_VECTOR(cnt, 5);
end if;
end if;
end process;
end cnt_behavior;


Выше счетчик часов. Ничего нового.

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

-- For CONV_STD_LOGIC_VECTOR:
use ieee.std_logic_arith.all;

entity bin2bcd_5bit is
port( bin:in std_logic_vector(4 downto 0);
bcd1:out std_logic_vector(3 downto 0);
bcd10:out std_logic_vector(3 downto 0)
);

end bin2bcd_5bit;

architecture converter_behavior of bin2bcd_5bit is
begin
process(bin)
variable i : integer range 0 to 23;
variable i1 : integer range 0 to 9;
begin
i := conv_integer(bin);
i1 := i / 10;
bcd10 <= CONV_STD_LOGIC_VECTOR(i1, 4);
i1 := i rem 10;
bcd1 <= CONV_STD_LOGIC_VECTOR(i1, 4);
end process;
end converter_behavior;


Преобразователь Binary в BCD, по сути, просто разбивает одно бинарное число на два, каждое из которых представляет разряд десятичного числа. Из новинок – оператор rem, остаток от деления. Аналогично написан и преобразователь для шести бит, его приводить не буду.

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

entity BCD_to_7seg is
port(
BCD:in std_logic_vector(3 downto 0);
seg:out std_logic_vector(6 downto 0)
);

end BCD_to_7seg;

architecture conv_behavior of BCD_to_7seg is
begin
process(BCD)
begin
if BCD = "0000" then seg <= "0000001";--0
elsif BCD = "0001" then seg <= "1001111";--1
elsif BCD = "0010" then seg <= "0010010";--2
elsif BCD = "0011" then seg <= "0000110";--3
elsif BCD = "0100" then seg <= "1001100";--4
elsif BCD = "0101" then seg <= "0100100";--5
elsif BCD = "0110" then seg <= "0100000";--6
elsif BCD = "0111" then seg <= "0001111";--7
elsif BCD = "1000" then seg <= "0000000";--8
elsif BCD = "1001" then seg <= "0000100";--9
else seg <= "1001001";--err
end if;
end process;
end conv_behavior;


Преобразователь одного разряда в семисегментный код, реализованный простой таблицей истинности. Сам семисегментный код представляет собой битовое представление горящих сегментов на индикаторе в порядке abcdefg, в моем случае еще и инверсное.

image


Вот и реализовали все блоки, осталось их соединить вместе. Приведу Главную схему и расшифровку символа bin2seg, который реализован также в виде схемы для экономии места.

Главная схема:
(клик-клик)

(Как видно на схеме, я вывел счет секунд на зеленые светодиоды, опять же для наглядности)

bin2seg:
(клик-клик)


Проект готов, можно компилировать и тестировать. Хотел рассказать про моделирование работы схемы в квартусе, но что-то итак много получилось, будет интерес – напишу в следующей статье. Теперь последний этап – надо ассоциировать наши виртуальные входы/выходы с реальными ножками ПЛИС.

Ассоциацию можно проводить после компиляции в меню Assignments – Pins. В появившемся окне вы увидите схемы расположения пинов на микросхеме и под ней список пинов проекта. Изменяя поле Location, сопоставляем внутренние входы реальным ножкам. В моем случае я смотрю имена пинов по схеме моей платы, а в случае своей разработки вы будете знать имена пинов. Последний шаг – прошивка, она осуществляется из меню Tools – Programmer, кнопкой старт (программатор должен быть подключен и драйвера установлены).

Ну, вроде все. В заключение хочу сказать, что при наличии интереса публики к данной теме непременно напишу еще статьи. Например, как доделать этот проект до нормальных часов с настройкой и будильником или что-то новое, у меня уже есть замыслы :)



Обещанные ссылки:
www.altera.com — всевозможная документация и реклама продуктов
www.bsuir.by/vhdl/reference — справочник VHDL
altera.ru — наш представитель компании Altera, я там заказывал стартер-кит
+ я использовал книжку ДЖ.Р.Армстронга Моделирование цифровых систем на языке VHDL.
Для тех, кто возьмется собирать сам, прикладываю схему моей платы. (кач)
А также бонусом – проект таймера (кач-кач)
Tags:ПЛИСCPLDFPGAclocktimercyclone
Hubs: DIY
Total votes 93: ↑85 and ↓8+77
Views74K

Popular right now

Top of the last 24 hours