Что такое VHDL. Простейший пример создания логического элемента в ПЛИС

Вот вокруг говорят ПЛИС, ПЛИС… Понятно что это микросхема такая… А как в нее электрическую схему заталкивать? Для этого существует несколько способов, один из которых это описание схемы устройства на специальном языке описания аппаратуры. Таких языков существует несколько: Verilog, AHDL, VHDL и наверное еще есть, но мне не попадались. Эти три вроде самые распространенные. Вот о VHDL я и попробую рассказать.

Почему VHDL? Он как и Verilog поддерживается большинством производителей ПЛИС, в то время как AHDL разрабатывался сугубо для Альтеры. Ну и в свое время по VHDL мне удалось достать литературу на русском языке. ПЛИС достаточно сложные устройства и читать кроме даташита еще и описание языка на буржуйском — рвет мозг на мелкие кусочки.

Чем VHDL отличается от обычных языков программирования типа Си, Паскаля и т.д.? Самое главное отличие в том, что VHDL описывает параллельные процессы!!! Если код на Си или Паскале у нас выполняется по очереди команда за командой и надо изощряться с таймерами и прерываниями чтоб обработать разные куски кода «одновременно», то на VHDL разные блоки программы выполняются параллельно друг другу, но в тоже время в VHDL есть часть комманд, которые выполняются последоватеьно. Поэтому структура программы в корне отличается от привычной микроконтроллерной.

Структура программы
Процесс программирования на VHDL чем то напоминает создание принципиальной схемы устройства.
Шаг 1: Включение в код используемых библиотек.
Шаг 2: Описание точек входа и точек выхода устройства (аналогично входам и выходам принципиальной схемы всего устройства).
Шаг 3: Описание точек входа и выхода элементов входящих в устройство (аналогично назначению функций ногам контроллера и другой логике в схеме)
Шаг 4: Описание архитектуры элементов входящих в устройство (вроде подбора логики типа ИЛИ-НЕ, И-НЕ, вобщем описание того как выход элемента завязан с его входом)
Шаг 5: Описание архитектуры всего устройства (типа соединения проводниками всех элементов схемы)

Чтобы это все было не голословно, попробуем создать с помощью VHDL простой элемент, например 16-ти разрядный счетчик, который считает по переднему фронту импульса.

Что еще нам надо знать, чтобы переходить к созданию первой программы на VHDL?
Основные структуры данных, с которыми мы можем работать. В литературе они называются классами объектов. Их три:
— константы
— переменные
— сигналы
Константы имеют тот же смысл, что и в других языках и определяются ключевам словом "constant":

constant MyConst: integer:=32; -- Константа с именем MyConst типа integer равная 32

Символы "--" означают комментарий

ВАЖНО: в VHDL нет разницы между строчными и прописными буквами в идентификаторах!!! То есть константы MyConst и mYcONST это на самом деле одна и таже константа!!!
Есть еще одно важное правило: идентификатор не должен оканчиваться подчеркиванием!!!


Переменные имеют практически тот же смысл, что и в других языках. Определяются ключевым словом "variable":

variable i: integer range 0 to 31; 

Переменная i типа integer принимающая значения в диапазоне от 0 до 31. Описание диапазона позволяет точно определить разрядность переменной, что существенно экономит ресурсы кристалла. Присваивание переменной выполняется с помощью знака ":=".

Сигналы это очень важный класс в VHDL. Они похожи на переменные, НО физически имеют смысл проводников на печатной плате. Это значит, что сигнал всегда имеет некоторое значение.
Описываются сигналы с помощью ключевого слова "signal":

signal CLK, RESET : bit;
signal data_out : bit_vector (15 downto 0);

Первая строчка описывает два сигнала типа bit, вторая строчка описывает сигнал представленный в виде 16ти-разрядной шины. Причем нумерация бит (15 downto 0) или (0 to 15) имеет значение при операциях с сигналами. Назначение сигналов выполняется с помощью знака "<=".

ВАЖНО!!! Все операции с сигналами выполняются параллельно. Тоесть код вида

a<=b+c;
d<=a-c;

совершенно не означает, что d у вас всегда будет равно b, как это было бы в Си или Паскале. Данные команды будут выполняться параллельно и существует достаточная вероятность что d будет некоторое время неравно b. «Некоторое время» зависит от задержек прохождения сигнала через различные элементы структуры ПЛИС и от прочих факторов.
Итак приступим:

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



Внешний квадрат (one_element) — это наша ПЛИС.
К ней слева подходят 3 входных сигнала:
Main_clk — тактовый сигнал, по которому собственно и работает наш счетчик,
Res — сигнал сброса счетчика в 0,
Enab — Разрешающий сигнал, наличие которого разрешает работу счетчика.
Справа выходной сигнал размерностью 16 бит (integer range 0 to 65535 дает нам 16 разрядов)
Это те сигналы, которые физически будут приходить непосредственно на ноги нашей ПЛИС.

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

Теперь, когда с общей структурой элемента ясно запишем все это с помощью VHDL. По шагам, которые были описаны в начале статьи.

Шаг 1 Включение в код используемых библиотек:
Как и во всех языках программирования, в VHDL есть стандартные библиотеки. Крайне рекомендуется вставлять следующий код:

library IEEE;
use IEEE.std_logic_1164.all;

Данная библиотека дает возможность присваивать сигналам типа bit не только значения 0 и 1 но и еще 7 дополнительных значений включая Z-состояние. Более детально в следующих статьях или в литературе, чтобы не перегружать материал.

Шаг 2 Описание точек входа и точек выхода устройства:
В литературе это называется описанием интерфейса.
Интерфейс любого элемента описывается с помощью ключевого слова entity и представляет собой следующую конструкцию:

entity имя_элемента is
       port (
             список входных сигналов : in тип сигналов;
             список выходных сигналов : out тип сигналов
            );
end имя_элемента;


Так как, в соответствии со структурой программы, в данном шаге мы описываем интерфейс всего устройства, значит входными и выходными сигналами у нас будут все линии подходящие к блоку «one_element» изображенные на схеме. Запишем их на VHDL:


entity one_element is	-- Счетчик 
	port (	
		main_clk, res, enab : in bit; -- вход временной синхронизации, ас. сброс, разрешение.
        counter_out : out integer range 0 to 65535 --выход значения частоты
	);
end one_element;


Шаг 3 Описание точек входа и выхода элементов входящих в устройство:
Интерфейс элементов входящих в состав схемы описывается аналогично интерфейсу всего устройства.
ВАЖНО: Если в схему входит несколько однотипных элементов (например 2 одинаковых счетчика и 3 одинаковых регистра), то в данной части программы мы описываем по одному элементу каждого типа (в продолжение примера — один счетчик и один регистр).
У нас в схеме счетчик только один, поэтому мы его и опишем:


entity counter is -- описание 16-разрядного счетчика по переднему фпронту
	port (
		c_in,enab_c,res_c:in bit;
		c_out:out integer range 0 to 65535
	);
end counter;

Шаг 4 Описание архитектуры элементов входящих в устройство:
Архитектура элементов описывается с помощью ключевого слова «architecture» и представляет собой конструкцию типа:

architecture имя_архитектуры of имя_элемента is
begin
        process(список запуска)
        variable список переменных;
        begin
            описание зависимости выходных сигналов от входных
        end process;
end имя_архитектуры;


Ключевое слово «process» означает описание параллельного процесса (обработки наших входных сигналов для получения выходных), который активируется «списком запуска».
ВАЖНО!!!Все логические блоки описанные как процесс выполняются параллельно друг другу!!!"
Список сигналов" это перечень сигналов, изменение которых активирует выполнение процесса.
Звучит очень сухо и заумно, но надеюсь что при разборе примера будет понятней.
У нас счетчик работает по переднему фронту сигнала c_in. Значит нам надо контролировать изменение этого сигнала и при обнаружении переднего фронта активировать счетчик.
Запишем это на VHDL:


architecture count of counter is -- архитектура данного счетчика
begin
	process (c_in, res_c)
	variable cnt:integer range 0 to 65535;
	begin
		if (res_c='0') then --если низкий уровень Reset - сбрасываем счетчик
                        cnt:=0;
		elsif (c_in'event and c_in='1') then -- если передний фронт - считаем
			if (enab_c='1') then cnt:=cnt+1;
			end if;
		end if;
		c_out<=cnt;
	end process;
end count;


Запись if (c_in'event and c_in='1') then выполняет функцию детектора переднего фронта сигнала. Ключевое слово 'event это стандартный атрибут сигнала c_in, который определяет изменение сигнала. Каждый сигнал в проекте имеет стандартный набор атрибутов. Дословно нашу запись можно прочитать так: Если c_in изменился и при этом равен 1. Остальной код процесса работает как и в других языках программирования. Разве что стоит обратить внимание на запись c_out<=cnt; которая назначает выходному сигналу c_out значение переменной cnt
Все предыдущие шаги — это была подготовительная работа. Остался последний шаг:

Шаг 5 Описание архитектуры всего устройства:
Описание архитектуры всего устройства существенно отличается по структуре от описания архитектуры элемента, хотя производится так же с помощью ключевого слова «architecture»
В нашем примере эта часть выглядит так:


architecture a of one_element is -- архитектура всего устройства
	component counter  -- счетчик как компонент
	port (
		c_in,enab_c,res_c:in bit;
		c_out:out integer range 0 to 65535
		);
	end component;

begin
	mc: counter -- описание работы главного счетчика
	port map (c_in=>main_clk,enab_c=>enab,res_c=>res,c_out=>counter_out);
end a;



Как вы могли заметить у нас появилась новая конструкция component. Данная конструкция говорит о том, что элемент counter является компонентом, входящим в состав one_element, и содержит описание входных и выходных портов этого компонента.

Далее в операторных скобках begin — end идет самое важное: назначение входных и выходных сигналов устройства экземплярам компонентов входящих в данное устройство.
Эта часть записи равносильна соединению логическихэлементов проводниками. Т.к. у нас в проекте только один счетчик — то именно ему мы и назначаем все наши входные и выходные сигналы.
mc — это имя экземпляра счетчика.
port map — это команда назначения входных и выходных сигналов данному счетчику

На этом собственно все. Итоговый код который у нас получился выглядит так:


library IEEE;
use IEEE.std_logic_1164.all;


entity one_element is	-- Счетчик 
	port (	
		main_clk, res, enab : in bit; -- вход временной синхронизации, ас. сброс, разрешение.
        counter_out : out integer range 0 to 65535 --выход значения частоты
	);
end one_element;
entity counter is -- описание 16-разрядного счетчика по переднему фпронту
	port (
		c_in,enab_c,res_c:in bit;
		c_out:out integer range 0 to 65535
	);
end counter;

architecture count of counter is -- архитектура данного счетчика
begin
	process (c_in, res_c)
	variable cnt:integer range 0 to 65535;
	begin
		if (res_c='0') then 
                        cnt:=0;
		elsif (c_in'event and c_in='1') then 
			if (enab_c='1') then cnt:=cnt+1;
			end if;
		end if;
		c_out<=cnt;
	end process;
end count;

architecture a of one_element is -- архитектура всего устройства
	component counter  -- счетчик как компонент
	port (
		c_in,enab_c,res_c:in bit;
		c_out:out integer range 0 to 65535
		);
	end component;

begin
	mc: counter -- описание работы главного счетчика
	port map (c_in=>main_clk,enab_c=>enab,res_c=>res,c_out=>counter_out);
end a;


Счетчик готов.

P.S. Исправил код, внес уточнения замеченные в комментариях. Теперь сигнал сброса асинхронный.

P.P.S. Добавил видео работы элемента в железе:



Некоторые пояснения к видео:
Большая макетка с кучей проводов — это плата типа PINBOARD от Ди Хальта для микроконтроллеров AVR и ARM. На ней собран другой проект, не имеющий отношения к ПЛИС. Потому на ней куча проводов.
Я с этой платы использую питание 5 В, и секцию с ДИП-переключателями, чтобы притянуть входы макетной платы ПЛИС к +5 вольтам через резисторы 10k.
Так что не обращайте внимание на нее.


  • +8
  • 09 марта 2011, 23:30
  • Ultrin

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

RSS свернуть / развернуть
Сейчас буду пинать ногами))
Вы говорили что сами изучали язык и ждете критику, я тоже сам изучал язык, буду критиковать, или пояснять.

Назначение одного сигнала можно выполнить только один раз.
назначение сигнала можно выполнять сколько угодно раз, главное чтобы это было в одном процессе, это связано с физической реализацией, т. к. процессы могут выполнятся одновременно, то и значение сигнала тоже может изменится одновременно, поскольку сигнал эквивалентен проводнику на плате, то это не возможно.
Назначение сигнала происходит при выходе из процесса.

Сигналы нужны для связи между процессами одной архитектуры, т. к. переменные (variable) видны только внутри процесса.

По чему вы учились? у меня это была книга «Основы языка VHDL» Бибило П.Н.
0
На сколько я помню, (щас после переустановки винды софт весь снесен) если в одном процессе попытаться дважды выполнить назначение одного и того же сигнала MAXPLUS давал ошибку. Вот цитата из П.Н.Библо «Основы языка VHDL»: «Каждый источник сигнала есть драйвер. В каждом процессе сигнал должен иметь только один источник, иначе требуется специальная функция, которая будет определять значение сигнала, определяемого из нескольких драйверов.»
+1
busy <= '0';
wire_out <= '1';
if (start = '1') then
    busy <= '1';
    state := wire_0;
end if;

это часть кода из одного проекта (немного позже опишу его здесь) сигналу busy может быть назначено значение в 2 местах. Если условие if выполнилось тогда busy будет 1, в другом случае 0. Все компилируется в Квартусе.
0
Раз говорите, что можно, значит можно. Убрал из статьи.
0
Здесь Бибило П.Н. Говорит о другой ситуации. Вы его не правильно поняли. Вот кусок кода на vhdl. Попробуйте откомпилить. Получите сообщение об ошибке. (Переведите сообщение об ошибке)!

UUT_0: sch PORT MAP(
		in_sign => in_main(0), 
		clk => clk, 
		out_sign => in_temp(0)
   );

	UUT_1: sch PORT MAP(
		in_sign => in_main(1), 
		clk => clk, 
		out_sign => in_temp(1) 
   );

  
P1 :	process(clk,res)
			begin
				if(res='1') then
					in_temp <= (others => '0');
					i <= '0';

Ругаться будет на in_temp
0
Для примера можно было попроще взять что-нибудь(сумматор какой) и оставить атрибуты на потом.
0
Сумматорымного разбираются в литературе, хоть зачастую и заумно. И код надо заново придумывать. А на счетчик у меня код взятый из моих же экспериментов, когда я ПЛИСиной экспериментировал… По сложности он такой же, зато фишек показывает больше.
0
Verylog

Не-а))
0
исправил, спасибо.
0
Вообще для человека первый раз видящего VHDL эта статья вынос мозга :). Лучше взять пример просто реализовав какой нибудь элемент ИЛИ-НЕ. При этом не очень понятно зачем усложнять первый пример использованием структурного описания(использованием компонентов), запихнуть в железо счетчик можно и без внешнего элемента. Вообще неплохо было сразу оговорится, что есть операторы параллельные, а есть последовательные, оператор процесса — это специальный параллельный оператор, в котором размещаются последовательные операторы, для новичков это самый хороший вариант. Вообще все операторы похожи на Паскаль.
Был бы хороший показательный пример, если показать проектирование ЦУ в классическом варианте( например по таблице истинности) и показать параллель проектирования на VHDL, тогда сразу станут ясны преимущества проектирования на языке HDL. А так вообще радует, что появляются статьи в этом направлении.
Для тех кого интересует эта тема, могу порекомендовать почитать журнал Компоненты и Технологии, там очень много хороших примеров.
0
а вы не хотите помочь проекту своими статьями? если есть что сказать статьи очень легко пишутся (сам вчера написал свою первую, а сегодня хочу дописать вторую)
0
Чему вторая статья будет посвящена? А то щас начну писать и зря.
0
Не знаю, что за книжку вы читали, но она научила вас плохому.
+1
Зачем же так сразу, человек старался писал. Надо тогда вам свою статью написать. Чтобы плохому членов сообщества не учить :).
0
Да я уж понял, что не отвертеться… Пишу.
0
Спасибо за отзыв. Моя книжка 2002 года издания, может в новых более полная информация. Главное что я разворошил улей и привлек внимание к теме. А на истину в первой инстанции я не претендовал. Я сам только учусь…
0
Извините, если обидел. Не хотел.
А про улей это очень верно )
И очень хорошо.
0
Какие могут быть обиды, Анатолий. Общее дело делаем. Просто по ПЛИСам информация обычно очень тяжко вытягивается :D. И зачастую в таком виде, что там многократный термоядерный взрыв мозга…
0
Поправил ошибки в коде и добавил в статью видео.
0
Пережал видео, сейчас должно быть без тормозов.
0
Пробую вставить указанный код и получаю ошибки
Error: Top-level design entity «test» is undefined
хотя Top-Level задавал уже дважды…
может быть еще нужно чтото нажать? или в каком нить режиме это сделать?
0
  • avatar
  • WitGo
  • 02 сентября 2012, 20:16
если кому интересно, то на pyroelectro запилили серию видеоуроков для начинающих по CPLD
пока, правда, только 3 (1 вводный, и 2 практических) из запланированных 10 (9)…

www.pyroelectro.com/edu/fpga/
+1
Спасибо за информацию.
0
Всёж если проводить сравнение языков описания с Си, то Verilog гораздо к нему ближе.
Код на нём получается более компактным и удобно читаемым. Особенно чужой.
Хотя на вкус и цвет фломастеры разные.
А верхний уровень проекта всё равно блоками лучше делать. Так возни меньше и проще выводить наружу тестовые сигналы.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.