ВЕРНУТЬСЯ

Макарченко И.П.

AHDL - это очень просто!

Введение

Электроника давно перешагнула тот рубеж, когда разработка сложных цифровых схем требовала усилий огромных коллективов и серьезных финансовых вложений. Сейчас, когда существуют программируемые логические интегральные схемы (ПЛИС), разработка стала настолько простой, что сложнейшие схемы можно создавать за считанные часы силами одного инженера. Для этого в конце XX века были разработаны специальные языки описания аппаратуры, одним из которых и является AHDL.

Программирование на языке AHDL (ALTERA Hardware Design Language) не более сложно, чем на простых обычных языках программирования. Сложность его понимания не больше чем сложность понимания того же Паскаля.

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

Что надо знать в первую очередь о языке описания апппаратуры (что и есть HDL в английской аббревиатуре)? Первая и главная особенность состоит в том, что такой язык описывает не программу действий, а схему, которая будучи реализованной в железе, работает параллельно. Это означает, что порядок написания конструкций языка почти не имеет значения. Порядок конструкций имеет значение только когда эти конструкции описывают не аппаратуру, а предписывает некое действие для компилятора языка. (Компилятор в данном контексте - это сборщик схемы, реализуемой в ПЛИС.)

Например, написав:

	FOR i=1 TO 10 GENERATE
		A[i] = B[i];
	END GENERATE;
разработчик указывает компилятору, что в схемe надо сгенерировать 10 конструкций вида A[i] = B[i] при всех i от одного до десяти. То же самое можно написать и так:

	А[1] = B[1]; 
	А[2] = B[2];
	А[3] = B[3];
	А[4] = B[4];
	А[5] = B[5];
	А[6] = B[6];
	А[7] = B[7];
	А[8] = B[8];
	А[9] = B[9];
	А[10] = B[10];
и так:
	А[6] = B[6];
	А[7] = B[7];
	А[8] = B[8];
	А[9] = B[9];
	А[10] = B[10];
	А[1] = B[1]; 
	А[2] = B[2];
	А[3] = B[3];
	А[4] = B[4];
	А[5] = B[5];
и так:
	А[10..1] = B[10..1];
компилятору не важно, в каком порядке в его памяти появились записи для аппаратурных конструкций. Ему важна только схема соединений этих конструкций, а она то и задается оператором '=' на языке AHDL. Это не оператор присваивания в понятиях обычных языков, а "оператор соединения цепей", в котором слева стоит некий выход, к которому подключается логическая функция, стоящая справа, которая может быть как простым логическим выражением, так и сложной функцией, записываемой подобно функции в других языках.

Подобным способом можно написать:
	A = not (B and C);
	B = not (A and D);
И это будет описание простейшего RS-тригера, в котором входы C и D играют роль входов R и S, а выходы A и B - роль прямого и инверсного выходов тригера.

Заметив, что A = not (B and C); по сути есть стандартный элемент 2И-НЕ, составляющий так называемый "Базис Шеффера", можно легко сделать вывод, что вот этих то конструкций и достаточно, чтобы построить любую схему.

Например, защелка (latch) будет выглядеть так:
	A = !(B&Е); Е = !(D&C);
	B = !(A&F); F = !(!D&C);
	Q = A;
где C - вход разрешения, D - вход данных, Q - выход, символ & означает в AHDL операцию И, символ ! - логическую инверсию.

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

Во-первых это конструкции, относящиеся непосредственно к ПЛИС фирмы ALTERA - конструкции, описывающие внутренние структурные элементы ПЛИС. Наиболее распространенным таким элементом является D-тригер

Записывается он подобно функции в обычном языке: Q = DFF(Data,Clk,Res,Set);

Для AHDL не имеет значения регистр символов. Сигнал Q в данной конструкции является выходом. К нему подключен D-тригер DFF (D-Flip-Flop), a сигналы, записанные как входные параметры "функции" DFF являются входными сигналами тригера. DFF по сути эквивалентен одному тригеру микросхемы 555ТМ2 за исключением того, что скорость его работы внутри современных ПЛИС более чем на порядок выше и достигает 500-600 мегагерц в топовых моделях ПЛИС.

Для того чтобы "облегчить" работу разработчику, привыкшему иметь дело с обычной логикой, в альтеровских САПР-ах язык AHDL включает множество "макросов", являющихся аналогами известных микросхем. О них можно найти информацию в справочной системе MAX-Plus II. В Quartus II подобной справочной системы нет, и там идет отсылка к справочнику MAX-Plus, которого вполне достаточно для начала работы.

Тем не менее, подобное описание схем через аналоги устаревших микросхем является достаточно порочным. Во первых в нем легко запутаться. Во-вторых, далеко не всегда по названию микросхемы ясно, что же она делает, а изучать их отдельно теряет всякий смысл, когда можно легко написать собственную функцию средствами языка AHDL, так, что она будет достаточно прозрачна и ясна при повторном прочтении.

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

Приведу несколько примеров достаточно сложных схем описанных простыми конструкциями языка AHDL.

Мультиплексор шины:
CASE SEL[1..0] IS
	WHEN 0 => Q[7..0] = D1_[7..0];
	WHEN 1 => Q[7..0] = D2_[7..0];
	WHEN 2 => Q[7..0] = D3_[7..0];
	WHEN 3 => Q[7..0] = D4_[7..0];
END CASE;
В описании на "аналогах микросхем" это вылилось бы в четыре функции-аналога К555КП12. При этом наглядность была бы потеряна. Вероятность ошибок повышена во много раз, а схема, реализованная через "аналог старых микросхем" оказалась бы для компилятора намного сложнее, потому что компилятор пытается делать аналог как можно более "идентичным" прототипу, что даже далеко не всегда полезно для реализуемой схемы.

А здесь используется простейший оператор выбора, известный по иным языкам, и сразу видно, что в зависимости от сигнала на шине выбора SEL[] на выход Q[] подключается одна из четырех шин D1_[] ,D2_[] ,D3_[] или D4_[].

Другой пример:
VARIABLE
	CT[3..0] : DFF;
BEGIN
	CT[3..0].clk = CLK;
	CT[] = CT[] + 1; 
END;
Во-первых, здесь показано, как можно вводить массивы элементов, а именно массив тригеров, состоящий из 4-х DFF-ов, Во вторых, показано, как можно к таким массивам подключать сигналы сразу ко всем одноименным входам. Строчка CT[3..0].clk = CLK; означает, что на все входы clk четырех тригеров подан сигнал CLK. Здесь же подразумевается, что неподключенные входы триггеров, а именно входы сброса и установки остаются в состоянии "по умолчанию", для входов сброса и установки в DFF это логические единицы или VCC.

Первый пример с тригером можно было бы записать и так: Q = DFF(Data,clk,,); Опущенные параметры означают, что входы остаются в состоянии по умолчанию. Не все входы подобных элементов имеют такое состояние. Если в описании подключения тригера забыть сигнал CLK, то MAX-Plus начнет ругаться (А вот Quartus, к сожалению, этого не делает, и пропуск clk у тригеров становится источником долгих мучений в попытках запуска неработающих схем).

Но вернемся к предыдущему описанию. Последняя строчка в нем означает, что на входы четырех триггеров подается сумма состояния его выходов с единицей. Это не логическая единица, а самая натуральная. И это означает, что состояние триггеров на следущем такте увеличится на единицу, а вся схема есть не что иное, как двоичный счетчик. Кому-то может показаться, что это описание длиннее, чем было бы описание счетчика, выполненного с помощью какой-нибудь функции. Может, оно и так, но записав такую функцию в виде, например COUNTER(CLK), разработчик сам поставит себя в положение дурака, потому что ни разрядность, ни коэфициент счета для такого "счетчика" из написания никак не следует. А городить названия счетчиков, содержащие в себе описание функции сильно неудобно, потому что через минуту потребуется этому счетчику прикрутить RESET или синхронную загрузку, а в простом счетчике их просто нет. В нашем же примере это делается всего парой исправлений:
VARIABLE
	CT[3..0] : DFF;
BEGIN
	CT[].clrn = RESET;
	CT[3..0].clk = CLK;
	IF LOAD THEN CT[] = Di[]; ELSE CT[] = CT[] + 1; END IF;
END;
Как подключен RESET, должно быть ясно. А для синхронной загрузки используется еще одна конструкция языка AHDL. Хорошо известный из других языков оператор IF, который в данном случае играет роль двухканального мультиплексора. Когда на входе LOAD присутствует сигнал "TRUE" - логическая единица, на вход тригеров CT[] подаются данные со входов Di[], a когда LOAD опускается в ноль счетчик возвращается к счету, на входы тригеров подается увеличенное на единицу состояние самого счетчика. Человек, знакомый с программированием вполне способен добавить к этому описанию что-то свое. Например, можно заставить счетчик считать не по кругу от нуля до 15, a например, превратить его в двоично-десятичный счетчик, когда при появлении на выходе состояния 9, он на следующем такте сбрасывается в нуль.

Внутренних элементов ПЛИС, имеющих собственную отдельную конструкцию в языке AHDL не так много. Приведу небольшую табличку с ними:
Элементназваниеописаниепримечание
DFFD-тригерDFF(d,clk,clrn=vcc,prn=vcc)подобен одному тригеру микросхемы 555ТМ2
TFFT-тригерDFF(d=vcc,clk,clrn=vcc,prn=vcc).
DFFЕD-тригер с разрешениемDFF(d,clk,clrn=vcc,prn=vcc,ena=vcc)Тот же DFF, только clk срабатывает лишь при наличии логической единицы на входе ena
LATCHтригер-защелкаLATCH(d,ena=vcc)работает, как один тригер микросхемы 555ИР22
SOFTбуферSOFT(signal)логический буфер. Означает, что логическое выражение signal будет сформировано во внутренних цепях ПЛИС явно, что может быть полезно для улучшения "разводимости схемы"
CARRYПереносCARRY(signal)Указывает компилятору, что сигнал должен быть реализован с помощью сигнала переноса в логической ячейке.
LCELLлогическая ячейкаLCELL(signal)Указывает компилятору, что логическое выражение, записанное на месте signal будет выведено внутри ПЛИС как отдельный реальный выход логической ячейки ПЛИС
EXPрасшширительEXP(signal)Указывает компилятору, что логическое выражение, записанное на месте signal будет сделано внутри ПЛИС как отдельный реальный выход специального логического расширителя (экспандера), являющегося особенносатью архитектуры ПЛИС серии MAX9000, MAX7000, MAX5000 и MAX3000. Следует отметить, что сигнал на выходе EXP инвертирует значение входного сигнала.
TRIтрехстабильный выходTRI(d,ena=vcc)при ena=vcc сигнал data проходит на выход, иначе выход переключается в Z состояние
OPNDRNВыход с "открытым коллектором" (открытым стоком)OPNDRN(d)при d=gnd выход заземляется, иначе выход переключается в Z состояние
OPNDRN(d) эквивалентен TRI(gnd,!d)

clrn=vcc и подобные записи входных сигналов в описании означают, что vcc - значение входногосигнала по умолчанию.

Здесь описано далеко не всё. Более полный справочник можно найти в хелпе MAX-Plus II и в литературе [1,2] по AHDL.

Структура TDF - файла на AHDL

TDF - это Text Design File - В САПР MAX-Plus II и Quartus II он используется для хранения исходных текстов на языке AHDL.

Для того, чтобы файл воспринимался правильно, он должен иметь правильную внутреннюю структуру.

В простейшем случае эта структура может быть представлена тремя блоками:

1. Блок объявления входов/выходов
2. Блок объявления внутренних элементов схемы
3. Блок логического описания.

Остальные возможные части описания являются необязательными, и они будут рассмотрены ниже.

Напишем простейший AHDL исходник в формате TDF:

Секция объявления входов/выходов:



SUBDESIGN first_simple (
	IN1 	: input;
	IN2 	: input;
	OUT	: output;
);
кроме очевидно описанных здесь входов и выходов, может быть объявлен еще один тип вывода - bidir - трехстабильный вход/выход, который может использоваться в логической секции и как вход и как выход. Как выход трехстабильный вывод должен подключаться с помощью примитива TRI или OPNDRN, в противном случае при простом подключении к нему сигнала он превратится в обычный выход. Допускается не подключать к трехстабильному выводу ничего, тогда он станет обычным входом.

Так же следует помнить, что внутри ПЛИС трехстабильных шин нет, и если вывод bidir описанной схемы попадает не на наружный вывод ПЛИС, компилятор может выдать ошибку.

Секция объявления внутренних элементов схемы:



VARIABLE
	LOGIC1	: NODE; 
	LOGIC2	: LCELL; 
	TRIGER	: DFF;
Здесь объявлено, что сигнал LOGIC1 является простым "логическим проводом", который может даже не присутствовать явно внутри ПЛИС. Для сигнала LOGIC2 компилятор получает предписание реализовать его на отдельной логической ячейке. А сигнал TRIGER объявлен как выход тригера. Сам тригер при этом получает одноименное название, а его входы TRIGER.clk, TRIGER.d, TRIGER.clrn и TRIGER.prn становятся доступны в логической секции описания как входы, к которым можно подключать сигналы.

Для объявления группы сигналов, например, шины или регистра, объявляется массив с использованием квадратных скобок с указанием диапазона индексов.
	BUS[7..0]	: NODE;
	REG[3..0][15..0]	: DFF;
Здесь обявлена восьмиразрядная шина и группа из четырех шестнадцатиразрядных регистров, которые объявлены как двухмерный массив. Доступ к каждому элементу массива осуществляется через имя массива и индекс, заключенный в квадратные скобки.
	REG[][].clk = CLK;
	REG[0][7..0] = BUS[];
	REG[3][15] = (BUS[7..4] == 14);
В этом примере на весь массив триггеров подключен сигнал CLK, на вход первых восьми разрядов нулевого регистра подключена шина BUS[] (квадратные скобки без указания индексов означают подключение всего массива сигналов). И в третьей строчке ко входу 15-го бита третьего регистра подключеная логическая функция, принимающая значение единицы, когда старшая половина сигналов шины BUS содержит код 14.

Секция логического описания:



BEGIN
	LOGIC2 = IN1 xor TRIGER;
	LOGIC1 = IN2;
	TRIGER.clk = LOGIC1;
	TRIGER.d = LOGIC2;
	OUT = TRIGER;
END;
В логической секции все выходные сигналы схемы должны быть куда-то подключены. Если это будет не так, компилятор начнет выдавать предупреждения и ошибки в зависимости от серьезности последствий данной "забывчивости". Неиспользуемые выходы могут долго оставаться "висеть" неподключенными, и компилятор на это "не обидится". Если же к такому выходу будет что-то подключено на верхнем уровне иерархии схемы, сразу же возникнет ошибка, и такая схема просто не разведется.

Неподключенные явно входы компилятор пытается подключить в состояние "по умолчанию". Так входы сброса и установки тригера можно "забыть", и они автоматом подключатся к VCC. Чтобы подобным образом вели себя и входы описываемой схемы, в описании входов можно указывать состояни входа "по умолчанию"

	IN1 	: input = GND;
	IN2 	: input = VCC;
подобным образом можно поступать с резервными входами, чтобы в дальнейшем не отвлекаться на их явное подключение куда-либо в верхнем уровне иерархии схемы.

Все секции вместе:



SUBDESIGN first_simple (
	IN1 	: input = GND;
	IN2 	: input = VCC;
	OUT	: output;
)
VARIABLE
	LOGIC1	: NODE; 
	LOGIC2	: LCELL; 
	TRIGER	: DFF;
BEGIN
	LOGIC2 = IN1 xor TRIGER;
	LOGIC1 = IN2;
	TRIGER.clk = LOGIC1;
	TRIGER.d = LOGIC2;
	OUT = TRIGER;
END; 

Описанная таким образом схема представляет собой Т-тригер, который по каждому тактовому импульсу (IN2) меняет свое состояние на противоположное, если на входе разрешения (IN1) присутствует логическая единица. Если на входе разрешения логический ноль, тригер сохраняет свое состояние независимо от тактового сигнала.

В AHDL подобная схема не представляет особой ценности, так как существует аналогичный тригер TFF, и схема приведена лишь как пример простейшего TDF файла.

Если его загрузить в Quartus или MAX-Plus как главный файл проекта, то он откомпилируется в ПЛИС и займет одну логическую ячейку и три вывода (два входа и один выход).

Все объявления выводов схемы должны находиться в первой секции. Объявления элементов схемы - во второй, и логическое описание - в третьей.

Иерархическое построение схем на AHDL и Параметризация

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

Для решения этих проблем AHDL предлагает использовать иерархическую структуру описания, в которой один TDF файл включает в себя схему соединения внешних частей схем, описанных в других TDF файлах.

Для такого подключения используется записи:
INCLUDE "ext1.inc";
INCLUDE "ext2.inc";
Которые добавляются в самом начале файла, перед секцией с описанием выводов. И ext1 с ext2 становятся доступными для объявления как элементы схемы:
VARIABLE
	Shema1 : ext1;
	Shema2 : ext2;
BEGIN
В секции логического описания остается только соединить входы и выходы shema1 и shema2 как надо для проекта и подключить их к внешним выводам, описанным в первой секции TDF файла. Использовать в секции объявления элементов схемы один и тот же "инклюд" можно множество раз. Можно, например, объявить массив подобных частей схем, и компилятор вставит их в ПЛИС столько раз, сколько потребует разработчик.

В процессе разработки не редко возникает необходимость модифицировать некие параметры схемы, например, разрядность регистра или метод построения схемы. Для того, чтобы это можно было сделать, в AHDL существует понятие "параметров", которые описываются перед первой секцией рядом с "инклюдами" с помощью необязательной секции параметров. Выглядит это так:
PARAMETERS (
	WIDTH = 8,
	SHEET = 2
)
WIDTH и SHEET - это названия параметров, которые могут быть использованы в дальнейшем описании как некие константы. Например, можно написать:
	Din[WIDTH-1..0]	: input;
И это будет эквивалентно объявлению восьмибитной входной шины. В дальнейшем, если во внешней схеме понадобится эта же схема но с 16-разрядной шиной, будет достаточно лишь поменять параметр WIDTH в описании элемента схемы:
	Shema1 : ext1 WITH (WIDTH=16); 
И разработчик получает возможность использовать старую наработку с любой разрядностью. Если в описании элемента не указывать параметры, то они будут приняты "по умолчанию" равными тому, что было указано в файле нижнего уровня иерархии.

Для параметризации так же существует так называемая конструкция IF GENERATE. Выглядит она так:
--	здесь оператор IF интерпретируется компилятором!
	IF SHEET == 1 GENERATE
--	И если условие выполняется (SHEET - это не сигнал(!), а параметр, заданный вначале файла)
--	компилятор вставляет в ПЛИС часть схемы, описанную в этой секции оператора IF

--	здесь описание первой версии схемы

	ELSE GENERATE
--	Если же условие не выполняется, в схему ПЛИС вставляется эта часть

--	здесь описание второй версии схемы

	END GENERATE;
Секция ELSE GENERATE не обязательна, и в описании может находиться множество конструкций IF GENERATE, для проверки условий с параметрами и генерации соответствующих частей схемы.



ЛИТЕРАТУРА

1. Стешенко В.Б. ПЛИС фирмы ALTERA: проектирование устройств обработки сигналов. М.: ДОДЭКА, 2000.
2. Антонов А.П. Язык описания цифровых устройств AlteraHDL. М.: Радио-Софт, 2001.