Простенькие примеры на VHDL.

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

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

  • Ресурсы для реализации комбинаторной логики, т.е. структур без запоминания данных. Например дешифраторы, мультиплексоры, демультиплексоры, асинхронные регистры сдвига, компараторы, сумматоры и другие асинхронные структуры, не требующие ни сигналов синхронизации ни запоминания данных, через них сигнал проходит, как бы насквозь преобразованный по определенной функции и с определенной задержкой. В CPLD данный вид ресурсов представляет распределитель термов, а FPGA это функциональные генераторы(таблицы истинности «Look-Up Table», да простят меня поклонники Altera) и всякие там дополнительные мультиплексоры.
  • Ресурсы для реализации последовательных схем(Переключающих схем). В отличии от комбинаторных схем, данные обладают дополнительной способностью запоминать отдельные состояния переменных, что позволяет реализовать функции выходных состояний, зависящие не только от входных состояний, но и от предыстории. Такие схемы реализуются при помощи триггеров(каждый триггер может хранить бит информации). В CPLD и FPGA содержаться D-триггеры, которые могут работать, как в синхронном так и в асинхронном режиме, плюс на FPGA такой вид схем можно реализовывать на LUT, которые могут быть сконфигурированы как ОЗУ или регистры сдвига. Как раз тут и появляются всякие хитрые задержки до прихода синхросигнала и после прихода синхросигнала.

Далее рассмотрим простые примеры реализации комбинаторных и последовательных схем.

Первым делом рассмотрим комбинаторную схемку для преобразования десятичного числа, в код для семисегментного индикатора, выглядит он следующим образом:

Не знаю как уменьшить картинку :(.
Будем считать, что подключаем этот индикатор шиной, в которой 0-ой разряд A и 6-ой разряд G.
Как будет выглядеть программа:
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

entity Segm7 is 
 port(x : in std_logic_vector(3 downto 0);  -- на вход подается числа от 0 до 9, по этому 4 разряда
     y : out std_logic_vector(6 downto 0)); -- на выход 7 разрядов, точку в расчет не берем
end Segm7;
      
architecture A_Segm7 of Segm7 is
begin
  process(x) -- у нас всего один процесс, который запускается просто при изменении входного сигнала, никаких сигналов синхронизации не требуется
    begin
      case(x) is -- в зависимости от входной комбинации, выдаем комбинацию на выход, у меня 0-горит, а 1-не горит 
       when x"0" => Y <= "1000000";
       when x"1" => Y <= "1111001";
       when x"2" => Y <= "0100100";
       when x"3" => Y <= "0110000";
       when x"4" => Y <= "0011001";
       when x"5" => Y <= "0010010";
       when x"6" => Y <= "0000010";
       when x"7" => Y <= "1111000";
       when x"8" => Y <= "0000000";
       when x"9" => Y <= "0010000";
       when others => Y <="1111111"; -- во всех остальных случаях у меня ничего не горит
      end case;
    end process;
end A_Segm7; 


Ну тут вроде все просто. Входной порт я подцепил к кнопкам, а выходной порт к семисегментному индикатору. Посмотрим результат, прошу прощения за качество, снимал на телефон:



Пойдем дальше и создадим устройство для моргания светодиодом. Как уже описывалось ранее на ПЛИС нет аппаратно реализованных таймеров и всякой другой хрени других полезных устройств, по-этому их надо создавать самому по мере надобности.
Будем решать задачу, хочется менять состояние светодиода с частотой 1 Гц. Для этого надо понизить частоту задающего кварца до 1 Гц и выдать сигнал на светодиод, сделать это можно при помощи счетчика, который считает количество тактов кварца. У меня на отладочной плате кварц на 2 МГц, значит для того что бы обеспечить частоту в 1 Гц надо посчитать 2 млн. импульсов, после этого сбрасывать счетчик и моргать лампочкой. Вроде идея понятно, посмотрим как должно выглядеть наше устройство:


Каждый блок представляет собой процесс. Реализации счетчика представляет собой последовательную схему, так как требуется помнить предыдущее состояние и прибавлять к нему единицу, т.е. потребуется использования ресурсов ПЛИС в виде триггеров. Можно конечно реализовать счетчик и на комбинаторной логике, но это глупо да и потребует жуткого количества ресурсов(будет реализовано в виде кучи сумматоров константа+1), по этому используют триггеры для хранения значения счетчика. Для реализации счетчика требуются синхронные триггеры срабатывающие либо по фронту либо по срезу, что позволяет считать, однозначно, одно событие за такт синхросигнала. Как будет работать наша схема: процесс счетчика будет считать синхроимпульсы с кварца, а компаратор сравнивать их с 2 млн. и при достижении этого числа импульсов сбрасывать счетчик в ноль и менять значения на диоде, что позволит менять состояние диода с частотой ровно 1 Гц. Как будет выглядеть программа:
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;

entity LED is
  port( CLK : in std_logic; -- один порт для сигнала с кварца
        L_D : out std_logic); -- одни выход на светодиод
      end LED;

architecture A_LED of LED is
signal counter : std_logic_vector(20 downto 0):= (others => '0'); -- вот тут сигналы это не просто провода,
--так как мы собираемся хранить в нем значения счетчика, каждый разряд сигнала займет один триггер
signal LED_temp,res : std_logic := '0'; -- так как эти сигналы мы тоже используем для хранения разряда, они займут по триггеру каждый
begin
  count : process(CLK,res) -- первый процесс счетчика
  begin
    if(res = '1') then -- обнуляем счетчик, когда res = 1, как видно сброс асинхронный
      counter <= (others => '0');
    elsif(rising_edge(CLK)) then -- здесь ждем фронта сигнала и увеличиваем на единицу значение в счетчике, если будет фронт
      counter <= counter +1;
    end if;
  end process;
  
  L : process(counter)
  begin
    if(counter = 2000000) then -- проверяем достижения 2 млн. тактов
      res <= '1'; -- если достигли ставим сигнал сброса
      LED_temp <= not LED_temp; -- меняем значение в выдаваемого на светодиод, здесь как раз хорошо видно для чего нужен триггер
--( это реализуется на триггере, мы помним предыдущее значение и его изменяем)
    else
      res <= '0'; -- если еще не досчитали до 2 млн. подтверждаем отсутствие сброса
--если это не сделаем, то компилятор оптимизирует затраты
--он нам обеспечит 1 при 2 млн. и хрен знает что при всех остальных значениях, так как это не было оговорено
    end if;
  end process;
 
 L_D <= LED_temp; -- вот тут передаем значения из триггера на выход
 end A_LED;


Ну а теперь посмотрим результат:


Ну вроде светодиод меняет свое состояние и вроде даже 1 раз в секунду.

Теперь попробуем сделать так, чтобы светодиоды бегали. Делается это просто, заменим инверсию состояния диода на оператор который будет сдвигать значения выдаваемые в порт.
Смотрим пример:
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;

entity LED is
  port( CLK : in std_logic;
        L_D : out std_logic_vector(3 downto 0)); -- поправил у меня 4 светодиода подключены
      end LED;

architecture A_LED of LED is
signal counter : std_logic_vector(20 downto 0):= (others => '0');
signal LED_temp : std_logic_vector(3 downto 0) := (0 => '1',others =>'0'); -- поправил на 4 светодиода
signal res : std_logic := '0';
begin
  count : process(CLK,res)
  begin
    if(res = '1') then
      counter <= (others => '0');
    elsif(rising_edge(CLK)) then
      counter <= counter +1;
    end if;
  end process;
  
  L : process(counter)
  begin
    if(counter = 2000000) then
      res <= '1';
    LED_temp <= LED_temp(2 downto 0) & LED_temp(3); -- новый красивый вариант от PetrovichKR 
-- старый топорный вариант led_temp <= to_stdlogicvector(to_bitvector(led_temp) rol 1); -- здесь пришлось сконвертировать std_logic в bit_vector,
--так как функция rol работает только с этим типом и unsigned
-- вращаю выходной вектор по кругу налево, постоянно смещая на 1 разряд 
   else
      res <= '0';
    end if;
  end process;
 
   L_D <= led_temp; -- выдаю информацию из регистров в порт
 
 end A_LED;


Смотрим результат:


Вот так вроде неплохо.

К чему написал первый абзац, надо аккуратней использовать понятия переменных и сигналов — это не одно и то же, да и всякие другие понятия. Я прочитал все статьи которые написаны по ПЛИС, каждый использует терминологию какую хочет и если бы я ничего не понимал в ПЛИС то вообще бы поплыл и запутался, это естественный недостаток, когда пишет одновременно много человек. Так что давайте внимательней перечитывать статьи друг друга и вырабатывать единое понимание всех вещей:) — ну это так, лирическое отступление.

Теперь вспомним, что у CPLD в каждой макроячейке содержится только один триггер, а ячеек обычно не больше 512, так что особо не разгонишься реализовывать последовательные схемы, так как у нас всего получается 512 бит памяти, зато у них приличный ресурс для реализации всяких там комбинаторных схем, плюс могут быть петли обратной связи для более эффективного использования распределителя термов. Ну а у FPGA, если мне не изменяет память, на один LUT по два триггера, можно реализовывать большие последовательные схемы, правда и цена за это соответствующая.
Какой из этого всего вывод — надо хорошо представлять, во что может превратится та или иная строка кода при синтезе для правильного использования ресурсов ПЛИС.

Как то так в общем.

Решил поддаться модным тенденциям поучаствовать в конкурсе и занятся рекламой :).

  • +9
  • 26 марта 2011, 17:30
  • opolo84

Комментарии (32)

RSS свернуть / развернуть
Я конечно понимаю, что писать зачеркнутым текстом сейчас модно, но это разве не уменьшает читаемость статьи, в таких-то количествах?
+1
Во во… Я тоже хотел попросить исправить всё в нормальный читабельный вид… а то сидиш как дурак по 2 раза читаеш и стараешся понять чего автор хочет этим сказать…
+1
Хотел пошутить, но тогда если убирать зачеркнутое смысл первого абзаца пропадет. По поводу того что сложней воспринимается полностью согласен.
0
Ну вроде моргает и вроде даже 1 раз в секунду.
Раз в две секунды (0.5 Гц) на самом деле. В принципе это и очевидно — по сигналу в один герц ты переключаешь состояние выхода — по сути это еще один каскад двоичного счетчика и деление частоты соответсвенно в 4М раз.
асинхронные регистры сдвига,
Я не уверен что это такое, но разве регистры сдвига — устройства комбинаторные?
0
  • avatar
  • Vga
  • 26 марта 2011, 18:54
По поводу миганий, если быть очень абсолютно точным то да, диод загорается раз в 2 минуты.
Асинхронный регистр сдвига стр.730 первый том — Титце У., Шенк К. Полупроводниковая схемотехника. 12-е изд. Том I,II: Пер. с нем. – М.: ДМК
Пресс, 2008. — всеобъемлющая книга, рассматривается все, начиная с диодов и заканчивая цифровой фильтрацией. Так как сам не знаю :)
0
Подскажите, есть ли смысл использовать ПЛИС для реализации драйверов под сигнальные реле (coil ~20mA,12V)? Основной критерий — стоимость.
0
~20mA — не переменный ток, а «приблизительно».
0
Уточнения.
"есть ли смысл" заменить на «возможно-ли»
~20mA не переменный ток, а «приблизительно».
0
Если этих реле 50 шт. тогда есть. Конечно на каждое реле надо будет по транзистору, поскольку 12В для ПЛИС это много и ток тоже не очень мал.
0
Как тут правильно уже ответили, все зависит от задачи и действительно придется вешать транзисторы.
0
Насчет статьи — 5 баллов однозначно. Самому резала глаза та вольность определений и кода, которые допускали авторы других статей. Нужно помнить, что VHDL — это не язык программирования, а язык описания цифровых схем. Соответственно, к нему должен быть иной подход.
Насчет последнего примера — я бы иначе реализовал сдвиг в шине светодиодов:
LED_temp <= LED_temp(2 downto 0) & LED_temp(3);

Оператор '&' в данном случае выполняет роль объединения двух сигналов в одну шину, причем первый сигнал поместится старшие разряды шины, второй сигнал — в младшие. Остальные пояснения, думаю, не требуются.
Имхо, так код выглядит более понятно и элегантно.
0
Хороший вариант, спасибо, совсем не подумал что можно так сделать, я помикроконтроллерной привычке сохранял старший бит в переменную и делал как вы, а потом в младший записывал из переменный, в принципе при синтезе даст тот же результат, но выглядело не красиво, по этому так замутил, щас добавлю как вариант. А ваш вариант и показать не стыдно :)
0
Хотя как вариант примера, того что переменные могут исчезать при синтезе тоже неплохо. У меня просто нет нормальной среды, где было бы хорошо видно результаты синтеза.
0
Подскажите, правильно ли я понял:
Первоначальное значение LED_temp = 1000 в двоичном коде. После выполнения операции
LED_temp <= LED_temp(2 downto 0) & LED_temp(3);
у нас в старшие разряды присвоится значение 000, а бывший старший разряд присвоится младшему и в результате будет LED_temp = 0001.?
0
Да все правильно, только начальное состояние 0001, так как вектор убывающий и 0-ой разряд стоит справа.
0
Тоесть в строке
signal LED_temp : std_logic_vector(3 downto 0) := (0 => '1',others =>'0');
запись 0 => '1' означает присвоение единице нулевому разряду. Блин, это я не внимательный… Спасибо.
0
Вообще, я сам до конца не понимаю как работают эти начальные присваивания, в литературе написано, что при синтезе они просто игнорируются, да и тот синтезатор которым я пользуюсь так и делает он пишет, что игнорирует это значение(может это только для CPLD, FPGA я реально не программировал), то что начальные значения не игнорируются при синтезе я узнал только из статьи Anatol, по-этому для единства изложения везде начал их вписывать, ну а при моделировании это просто обязательно.
0
Статья хорошая и годная.По ПЛИС пока вы лучший пока здесь, надеюсь другие научаться правильно и логично написать читая вас.Удачи!
0
Спасибо.
0
Кажется ошибка в коде:
when x«8» => Y <= «1000000»;
0
Да спасибо, поправлю.
0
Будем решать задачу, хочется менять состояние светодиода с частотой 1 Гц. Для этого надо понизить частоту задающего кварца до 1 Гц и выдать сигнал на светодиод, сделать это можно при помощи счетчика, который считает количество тактов кварца
В Xilinx можно сделать и без счетчиков, там есть такой блок DCM, которы является аппаратным делителем/умножителем тактовой частоты, с помощью однго или нескольких таких блоков можно сформировать практически любую частоту (даже дробную тактовой) при этом не используюутся логические блоки, а сформированный сигнал можно смело пропускать по тактовым цепям (у них быстродействие на прохождение сигнала больше на порядок обычно)
0
Абсолютно верно, только я делал на CPLD, а там таких вещей нет, так что все надо делать ручками :)
0
Ну тогда претензий нет;) просто лучше указывать поточнее, а то некоторые начинающие идут проторенными дорогями, не пытаясь посмотреть что там еще интересного есть;)
0
это самая путевая статья из всех что я видел за последние 3 часа…

пожалуй закажу себе узь270 и буду пробовать…

а по поводу софтовой эмуляции (пока железо будет идти) — есть что нить понятное и простоописанное?
0
  • avatar
  • WitGo
  • 02 сентября 2012, 15:50
L : process(counter)
  begin
    if(counter = 2000000) then -- проверяем достижения 2 млн. тактов
      res <= '1'; -- если достигли ставим сигнал сброса
      LED_temp <= not LED_temp; -- меняем значение в выдаваемого на светодиод, здесь как раз хорошо видно для чего нужен триггер
--( это реализуется на триггере, мы помним предыдущее значение и его изменяем)


вот только отсинтезится всё это не в триггер, а в латч :)
0
А с каких пор Latch перестал быть триггером?
0
С тех самых пор, как Вы взялись за FPGA.
0
когда разбирался с ПЛИС и VHDL, накатал часы, там ещё ШИМ менял яркость от 0 до 16 в такт секундам. кнопками можно время устанавливать. Платка была со Spartan3. Если кто хочет, могу выложить. Для начинающих вполне прокатит.
0
Хорошая, годная статья. Ведь в любом программировании самое сложное — начать, понять синтаксис и принцип работы. Потом уже и FFT не покажется таким страшным: kanyevsky.kpi.ua/useful_core/behavioral_fft.html (кажется, там можно и покрасивее сделать, особенно бит-реверсную арифметику)
0
прошу помочь разобраться с адаптированной версией кода для мигающих светодиодов. нашел в сети переписанный на верилоге пример из статьи, см. ниже

module led2

(
input clk,
output [3:0] reg leds = 4'b1
);
reg [20:0] counter = 0;
always @(posedge clk)
begin
if(counter == 0)
begin
leds[3:1] <= leds[2:0];
leds[0] <= leds[3];
counter <= 2000000;
end
else
counter <= counter — 1'b1;
end
endmodule

в котором ошибка возникает из-за неправильного описания «leds», автор что то упустил или же у меня искаженное восприятие реальности? заранее признателен за помощь.
p.s. попытки сделать «бегущие огоньки» используя регистр сдвига пока не влезают в голову )) надо понять как это работает…
0
ошибка возникает из-за неправильного описания «leds»
Опиши так:
output reg [3:0] leds = 4'b1
0
jawert, спасибо. попробую
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.