|
В. Стешенко, А. Самохин
Школа разработки аппаратуры цифровой обработки сигналов на ПЛИС Занятие 5. Язык описания аппаратуры AHDL
Общие сведения о языке описания аппаратуры AHDL
Язык описания аппаратуры AHDL разработан фирмой Altera и предназначен для описания комбинационных и последовательностных логических устройств, групповых операций, цифровых автоматов (state machine) и таблиц истинности с учётом архитектурных особенностей ПЛИС фирмы Altera. Он полностью интегрируется с системой автоматизированного проектирования ПЛИС MAX+PlusII. Файлы описания аппаратуры, написанные на языке AHDL, имеют расширение “*.TDF” (Text design file). Для создания TDF-файла можно использовать как текстовый редактор системы MAX+PlusII, так и любой другой. Проект, выполненный в виде TDF-файла, компилируется, отлаживается и используется для формирования файла программирования или загрузки ПЛИС фирмы Altera.
Операторы и элементы языка AHDL являются достаточно мощным и универсальным средством описания алгоритмов функционирования цифровых устройств, удобным в использовании. Язык описания аппаратуры AHDL даёт возможность создавать иерархические проекты в рамках одного этого языка или использовать TDF-файлы, разработанные на языке AHDL, наряду с другими типами файлов. Для создания проектов на AHDL можно, конечно, пользоваться любым текстовым редактором, но текстовый редактор системы MAX+PlusII предоставляет ряд дополнительных возможностей для ввода, компиляции и отладки проектов.
Проекты, созданные на языке AHDL, легко внедряются в иерархиче-скую структуру. Система MAX+PlusII позволяет автоматически создать символ компонента, алгоритм функционирования которого описывается TDF-файлом, и затем вставить его в файл схемного описания (GDF-файл). Подобным же образом можно вводить в любой TDF-файл собственные функции разработчика и около 300 макрофункций, разработанных фирмой Altera. Для всех функций, включённых в макробиблиотеку системы MAX+PlusII, фирма Altera поставляет файлы с расширением “*.inc”, которые используются в операторе включения INCLUDE.
При распределении ресурсов устройств разработчик может пользоваться командами текстового редактора или операторами языка AHDL для того, чтобы сделать назначения ресурсов и устройств. Кроме того, разработчик может только проверить синтаксис или выполнить полную компиляцию для отладки и запуска проекта. Любые ошибки автоматически обнаруживаются обработчиком сообщений и высвечиваются в окне текстового редактора.
При работе с AHDL следует соблюдать так называемые “Золотые правила” (Golden Rules). Выполнение их позволит эффективно применять язык AHDL и избежать многих ошибок.
“Золотые правила AHDL”
- Соблюдайте форматы и правила присвоения имён, описанные в руководстве по стилям AHDL, чтобы программа была читаемой и содержала меньше ошибок.
- Несмотря на то, что язык AHDL не различает прописные и строчные буквы, Altera рекомендует для улучшения читаемости использовать прописные буквы для ключевых слов.
- Не применяйте вложенные конструкции условного оператора If, если можно использовать оператор выбора Case.
- Строка в TDF-файле может быть длиной до 255 символов. Однако следует стремится к длине строки, умещающейся на экране. Строки заканчиваются нажатием клавиши Enter.
- Новую строку можно начинать в любом свободном месте, то есть на местах пустых строк, табуляций, пробелов. Основные конструкции языка отделяются пустым пространством.
- Ключевые слова, имена и числа должны разделяться соответствующими символами или операторами и/или одним или более пробелами.
- Комментарии должны быть заключены в символы процента (%). Комментарий может включать любой символ, кроме символа %, поскольку компилятор системы MAX+PlusII игнорирует всё, заключённое в эти символы. Комментарии не могут быть вложенными.
- При соединении одного примитива с другим используйте только “разрешённые” связи между ними, не все примитивы могут соединяться друг с другом.
- Используйте только макрофункции EXPDFF, EXPLATCH, NANDLTCH и NORLTCH, входящие в макробиблиотеку системы MAX+PlusII. Не создавайте свои собственные структуры перекрёстных связей. Избегайте многократного связывания вместе EXPDFF, EXPLATCH, NANDLTCH и NORLTCH. Многочисленные примеры этих макрофункций должны всегда разделяться примитивами LCELL.
- Если многочисленные двунаправленные или выходные выводы связаны вместе, разработчик не может использовать оператор Pin Connection для соединения выводов при функциональном моделировании с аппаратной поддержкой или функциональном тестировании.
- Нет необходимости создавать прототипы функций для примитивов. Однако разработчик может переопределить примитивы в объявлениях прототипов функций для изменения порядка вызова входов в вашем TDF-файле.
- Не редактируйте файл Fit. Если разработчик желает отредактировать назначения для проекта, необходимо сохранить сначала файл Fit как TDF-файл или сделать обратное назначение с помощью команды Project Back-Annotate и отредактировать их с помощью команд Chip to Device, Pin/LC/Chip и Enter Assignments.
- Если разработчик хочет загрузить регистр по определённому фронту глобального тактового сигнала Clock, фирма Altera рекомендует, когда регистр загружен, использовать для управления вход Clock Enable одного из триггеров типа Enable: DFFE, TFFE, JKFFE или SRFFE.
- Когда разработчик начинает работать с новым файлом проекта, сразу же необходимо задать семейство ПЛИС, на которые ориентирован проект, с помощью конструкции Family для того, чтобы в дальнейшем иметь возможность воспользоваться макрофункциями, специфичными для данного семейства. Если разработчик не задаст семейство, оно будет считаться таким же, как и в текущем проекте.
- Используйте опцию Design Doctor для проверки надёжности логики проекта во время компиляции.
- Предоставляемые по умолчанию фирмой Altera стили для логического синтеза имеют разные установки для разных семейств устройств, что обеспечивает более эффективное использование архитектуры каждого устройства. Когда разработчик использует какой-нибудь из этих стилей, его установки изменятся при пере-ходе к другому семейству устройств. После смены семейства необходимо проверить новые установки стиля.
Использование чисел и констант в языке AHDL
Числа используются для представления констант в булевых выражениях и уравнениях. Язык AHDL поддерживает все комбинации десятичных, двоичных, восьмеричных и шестнадцатеричных чисел.
На врезке 2 приведён файл decode1.tdf, который представляет собой дешифратор адреса, генерирующий высокий активный уровень сигнала разрешения доступа к шине, если адрес равен шестнадцатеричному числу 370h.
Врезка 2
SUDESIGN decode1
(
address[15..0] : INPUT;
chip_enable : OUTPUT;
)
BEGIN
chip_enable = (adress[15..0] == H"0370");
END;
|
В этом примере десятичные числа использованы для указания размерности массива бит, которым записывается адрес шины. Шестнадцатеричным числом H”0370” записано значение адреса, при котором обеспечивается высокий уровень сигнала.
В файле AHDL можно использовать константы для описательных имён разных чисел. Такое имя, используемое на протяжении всего файла, может быть более информативным, чем число; например, имя UPPER_LI несёт больше информации, чем число 103. В языке AHDL константы вводятся объявлением CONSTANT.
Преимущество использования констант особенно заметно, если одно и то же число используется в файле несколько раз. Тогда, если его нужно изменить, его меняют только один раз в объявлении константы.
Комбинационная логика
Как известно, логическая схема называется комбинационной, если состояния выходов являются только функциями входов независимо от момента времени. Комбинационная логика в языке AHDL реализована булевыми выражениями и уравнениями, таблицами истинности и большим количеством макрофункций. В число примеров комбинаторных логических функций входят дешифраторы, мультиплексоры и сумматоры.
Булевы выражения — это множества узлов, чисел, констант и других булевых выражений, выделяемых операторами, компараторами и, возможно, сгруппированных в заключающих круглых скобках. Булево уравнение устанавливает равенство между узлом или группой и булевым выражением.
В качестве примера на врезке 3 приведён файл boole1.tdf, в котором даны два простых булевых выражения, представляющих два логических элемента.
Врезка 3
SUBDESIGN boole1
(
a0, a1, b : INPUT;
out1, out2 : OUTPUT;
)
BEGIN
out1 = a1 & !a0;
out2 = out1 # b;
END;
|
Здесь выход out1 получается в результате логической операции И, применённой ко входу a1 и инвертированному входу a0, а выход out2 получается в результате применения логической операции ИЛИ к выходу out1 и входу b. Поскольку эти уравнения обрабатываются одновременно, последовательность их следования в файле не важна.
Узел, который объявляется в секции переменных VARIABLE в объявлении NODE, можно использовать для хранения промежуточных выражений.
Это полезно делать, если булево выражение повторяется несколько раз и его целесообразно заменить именем узла. Файл boole1.tdf можно переписать по-другому (врезка 4).
Врезка 4
SUBDESIGN boole2
(
a0, a1, b : INPUT;
out : OUTPUT;
)
VARIABLE
a_equals_2 : NODE;
BEGIN
a_equals_2 = a1 & !a0;
out = a_equals_2 # b;
END;
|
Здесь объявляется узел a_equals_2, и ему присваивается значение выражения ”a1 & !a0”. Если узел используется в нескольких выражениях, то его объявление помогает экономить ресурсы устройств.
Важным понятием AHDL является группа. Она может включать в себя до 256 элементов (бит), рассматривается как совокупность узлов и участвует в различных действиях как единое целое. В булевых уравнениях группа может быть приравнена булевому выражению, другой группе, одному узлу, VCC, GND, 1 или 0. В каждом случае значения группы разные.
Если группа определена, для краткого указания всего диапазона ставят две квадратные скобки [ ]. Например, группу a[4..1] можно кратко записать как a[ ].
Условная логика делает выбор между режимами в зависимости от логических входов. Для реализации условной логики используются операторы IF или CASE:
- В операторе IF оценивается одно или несколько булевых выражений и затем описываются режимы для разных значений этих выражений.
- В операторе CASE даётся список альтернатив, которые имеются для каждого возможного значения некоторого выражения. Оператор оценивает значение выражения и по нему выбирает режим в соответствии со списком.
Логика оператора IF
В качестве примера рассмотрим файл priority.tdf (врезка 5), в котором описан кодировщик приоритета, который преобразует уровень самого приоритетного активного входа в значение. Он генерирует двухразрядный код, показывающий вход с наивысшим приоритетом, запускаемый VCC.
Врезка 5
SUBDESIGN priority
(
low, middle, high : INPUT;
highest_level[1..0] : OUTPUT;
)
BEGIN
IF high THEN
highest_level[] = 3;
ELSIF middle THEN
highest_level[] = 2;
ELSIF low THEN
highest_level[] = 1;
ELSE
highest_level[] = 0;
END IF;
END;
|
Здесь оцениваются входы low, middle и high, чтобы определить, запущены ли они VCC. На выходе получится код, соответствующий приоритету того входа, который был запущен VCC. Если ни один вход не запущен, значение кода будет равно 0.
Логика оператора CASE
В качестве примера рассмотрим файл decoder.tdf (врезка 6), реализующий функции дешифратора, преобразующего код из двухразрядного в четырёхразрядный. В результате его работы два двухразрядных двоичных входа преобразуются в один “горячий код”, называемый так потому, что четыре его допустимых значения содержат по одной единице: 0001, 0010, 0100, 1000.
Врезка 6
SUBDESIGN decoder
(
code[1..0] : INPUT;
out[3..0] : OUTPUT;
)
BEGIN
CASE code[] IS
WHEN 0 => out[] = B"0001";
WHEN 1 => out[] = B"0010";
WHEN 2 => out[] = B"0100";
WHEN 3 => out[] = B"1000";
END CASE;
END;
|
Здесь группа входа code [1..2] может принимать значения 0, 1, 2, 3. В зависимости от реального кода активизируется соответствующая ветвь оператора, и только она одна, в данный момент времени. Например, если вход code [] равен 1, на выходе out устанавливается значение В”0010”.
Операторы IF и CASE похожи. Иногда использование любого из них приводит к одним и тем же результатам. Однако между этими двумя операторами существуют несколько важных различий:
- В операторе IF можно использовать любое булево выражение. Каждое выражение, записываемое в предложении, следующем за IF или ELSEIF, может не быть связанным с другими выражениями в операторе. В операторе CASE одно единственное выражение сравнивается с проверяемым значением.
- В результате интерпретации оператора IF может быть сгенерирована логика, слишком сложная для компилятора системы MAX+PlusII. В приведённом ниже примере показано, как компилятор интерпретирует оператор IF. Если a и b — сложные выражения, то инверсия каждого из них даст ещё более сложное выражение.
Рассмотрим описание типовых комбинационных схем на AHDL. Дешифратор содержит комбинаторную логику, которая преобразует входные схемы в выходные значения или задаёт выходные значения для входных схем. Для создания дешифратора в языке AHDL используется объявление таблицы истинности TABLE.
На врезке 7 приведён файл 7segment.tdf, представляющий собой дешифратор, который задаёт логику схемы управления светодиодами. Светодиоды отображают на семисегментном дисплее шестнадцатеричные цифры (от 0 до 9 и буквы от A до F).
Врезка 7
SUBDESIGN 7segment
(
i[3..0] : INPUT;
a, b, c, d, e, f, g : OUTPUT;
)
BEGIN
TABLE
i[3..0] => a, b, c, d, e, f, g;
H"0" => 1, 1, 1, 1, 1, 1, 0;
H"1" => 0, 1, 0, 0, 0, 0, 0;
H"2" => 1, 1, 0, 1, 1, 0, 0;
H"3" => 1, 1, 1, 1, 0, 0, 1;
H"4" => 0, 1, 1, 0, 0, 1, 1;
H"5" => 1, 0, 1, 1, 0, 1, 1;
H"6" => 1, 0, 1, 1, 1, 1, 1;
H"7" => 1, 1, 1, 0, 0, 0, 0;
H"8" => 1, 1, 1, 1, 1, 1, 1;
H"9" => 1, 1, 1, 1, 0, 1, 1;
H"A" => 1, 1, 1, 0, 1, 1, 1;
H"B" => 0, 0, 1, 1, 1, 1, 1;
H"C" => 1, 0, 0, 1, 1, 1, 0;
H"D" => 0, 1, 1, 1, 1, 0, 1;
H"E" => 1, 0, 0, 1, 1, 1, 1;
H"F" => 1, 0, 0, 0, 1, 1, 1;
END TABLE;
END;
|
В этом примере показаны все возможные шестнадцатиричные цифры (i[3..0]) и соответствующие состояния светодиодов (a, b, c, d, e, f, g), которые обеспечивают “начертание” цифры на дисплее. Изображение светодиодов на дисплее дано в виде комментария перед файлом.
На врезке 8 приведён файл дешифратора адреса decode3.tdf для шестнадцатиразрядной микропроцессорной системы.
Врезка 8
SUBDESIGN decode3
(
addr[15..0], m/io : INPUT;
rom, ram, print, sp[2..1] : OUTPUT;
)
BEGIN
TABLE
m/io, addr[15..0] => rom, ram, print, sp[];
1, B"00XXXXXXXXXXXXXX" => 1, 0, 0, B"00";
1, B"100XXXXXXXXXXXXX" => 0, 1, 0, B"00";
0, B"0000001010101110" => 0, 0, 1, B"00";
0, B"0000001011011110" => 0, 0, 0, B"01";
0, B"0000001101110000" => 0, 0, 0, B"10";
END TABLE;
END;
|
В данном примере существуют тысячи возможных вариантов входа (адреса), поэтому было бы непрактично сводить их все в таблицу. Вместо этого, можно пометить символом “Х” несущественные, не влияющие на выход, разряды. Например, выходной сигнал rom (ПЗУ) будет высоким для всех 16384 вариантов адреса addr[15..0], которые начинаются с 00. Поэтому вам нужно только указать общую для всех вариантов часть кода, то есть 00, а в остальных разрядах кода поставить “Х”. Такой приём позволит использовать в данном проекте меньше устройств и ресурсов.
Приведённый на врезке 9 пример decode4.tdf показывает использование стандартной параметризируемой функции lpm_decode в задаче разработки дешифратора, аналогичной примеру decode1.tdf.
Врезка 9
INCLUDE "lpm_decode.inc";
SUBDESIGN decode4
(
address[15..0] : INPUT;
chip_enable : OUTPUT;
)
BEGIN
chip_eneble = lpm_decode(.data[]=address[])
WITH (LPM_WIDTH=16, LPM_DECODES=2^10)
RETURNS (.eq[H"0370"]);
END;
|
Иногда для переменных удобно использовать значения по умолчанию. Можно определить значения по умолчанию для узла или группы, которые будут автоматически использоваться, если в файле их значения не будут заданы. Язык AHDL позволяет присваивать значение узлу или группе в файле неодно-кратно. Если при этом произойдет конфликт, система автоматически будет использовать значения по умолчанию. Если они не были заданы, используется значение GND.
Объявление значений по умолчанию DEFAULTS можно использовать для задания переменных в таблице истинности, операторах IF и CASE.
На врезке 10 приводится файл default1.tdf, в котором происходит оценка входов и выбор соответствующего ASCII-кода.
Врезка 10
SUBDESIGN default1
(
i[3..0] : INPUT;
ascii_code[7..0] : OUTPUT;
)
BEGIN
DEFAULTS
ascii_code[] = B"00111111";% ASCII question mark"?" % END DEFAULTS;
TABLE
i[3..0] => ascii_code[];
B"1000" => B"01100001"; % "a" %
B"0100" => B"01100010"; % "b" %
B"0010" => B"01100011"; & "c" %
B"0001" => B"01100100"; % "d" %
END TABLE;
END;
|
Если значение входа совпадает с одним из значений в левой части таблицы, код на выходе приобретает соответствующее значение ASCII-кода в правой части таблицы. Если входное значение не совпадает ни с одним из (левых) табличных, выходу будет присвоено значение по умолчанию B”00111111” (вопросительный знак).
В приведённом на врезке 11 файле default2.tdf показано, как при многократном присваивании узлу разных значений возникает конфликт и как он разрешается средствами AHDL.
Врезка 11
SUBDESIGN default2
(
a, b, c : INPUT;
select_a, select_b, select_c : INPUT;
wire_or, wire_and : OUTPUT;
)
BEGIN
DEFAULTS
wire_or = GND;
wire_and = VCC;
END DEFAULTS;
IF select_a THEN
wire_or = a;
wire_and = a;
END IF;
IF select_b THEN
wire_or = b;
wire_and = b;
END IF;
IF select_c THEN
wire_or = c;
wire_and = c;
END IF;
END;
|
В данном примере выход wire_or устанавливается равным a, b или c, в зависимости от входных сигналов select_a, select_b и select_c. Если ни один из них не равен VCC, то выход wire_or принимает значение по умолчанию, равное GND.
Если более одного сигнала (select_a, select_b или select_c) равны VCC, то wire_or равно результату логической операции ИЛИ над соответствующими входными сигналами. Например, если select_a и select_b равны VCC, то wire_or равно a ИЛИ b.
С сигналом wire_and производятся аналогичные действия, но он становится равным VCC, когда входные сигналы select равны VCC, и равен логическому И от соответствующих входных сигналов, если более, чем один из них равен VCC.
Рассмотрим реализацию логики с активным низким уровнем. Значение сигнала с низким активным уровнем равно GND. Такие сигналы могут быть использованы для управления памятью, периферийными устройствами и микропроцессорными чипами.
На врезке 12 приводится файл daisy.tdf, который представляет модуль арбитражной схемы для соединения гирляндой (daisy chain). Данный модуль делает запрос на доступ к шине для предыдущего (в гирлянде) модуля. Он получает запрос на доступ к шине от самого себя и от следующего (в цепочке) модуля. Доступ к шине предоставляется модулю с более высоким приоритетом.
Врезка 12
SUBDESIGN daisy
(
/local_request : INPUT;
/local_grant : OUTPUT;
/request_in : INPUT; % from lower priority % /request_out : OUTPUT; % to higher
priority % /grant_in : INPUT; % from higher priority % /grant_out : OUTPUT; % to lower priority %
)
BEGIN
DEFAULTS
/local_grant = VCC; % active-low output % /request_out = VCC; % signals should default % /
grant_out = VCC; % to VCC %
END DEFAULTS;
IF /request_in == GND # /local_request == GND THEN
/request_out = GND;
END IF;
IF /grant_in == GND THEN
IF /local_request == GND THEN
/local_grant = GND;
ELSIF /request_in -- GND THEN
/grant_out = GND;
END IF;
END IF;
END;
|
Все сигналы в данном файле имеют активный низкий уровень. Фирма Altera рекомендует помечать как-нибудь имена сигналов с низким активным уровнем, например, первым символом в имени ставить “/”, не являющийся оператором, и использовать его постоянно.
В операторе IF проверяется, является ли модуль активным, то есть равен ли он GND. Если это так, то реализуются действия, записанные в операторе IF.
В объявлении по умолчанию DEFAULTS считается, что сигналу присваивается значение VCC, если он не активен.
Система MAX+PlusII позволяет конфигурировать порты ввода/вывода (I/O) в устройствах Altera как двунаправленные порты. Двунаправленный вывод задаётся как порт BIDIR, который подсоединяется к выходу примитива TRI. Сигнал между этим выводом и буфером с тремя состояниями является двунаправленным, и его можно использовать в проекте для запуска других логических схем.
Приводимый на врезке 13 файл bus_reg2.tdf реализует регистр, который делает выборку значения, найденного на шине с тремя состояниями, а также может передать обратно на шину хранимое значение.
Врезка 13
SUBDESIGN bus_reg2
(
clk : INPUT;
oe : INPUT;
io : BIDIR;
)
BEGIN
io = TRI(DFF(io, clk,, ), oe);
END;
|
Двунаправленный сигнал io, запускаемый примитивом TRI, используется в качестве входа d для D-триггера (DFF). Запятые в конце списка параметров отделяют места для сигналов триггера clrn и prn. Эти сигналы по умолчанию установлены в неактивное состояние.
Двунаправленный вывод можно также использовать для подсоединения TDF-файла более низкого уровня к выводу с высоким уровнем. Прототип функции для TDF-файла более низкого уровня должен содержать двунаправленный вывод в предложении RETURNS. В приведённом на врезке 14 файле bidir1.tdf даны четыре примера использования макрофункции bus_reg2.
Врезка 14
FUNCTION bus_reg2 (clk, oe) RETURNS (io);
SUBDESIGN bidir1
(
clk, oe : INPUT;
io[3..0] : BIDIR;
)
BEGIN
io0 = bus_reg2 )clk, oe);
io1 = bus_reg2 )clk, oe);
io2 = bus_reg2 )clk, oe);
io3 = bus_reg2 )clk, oe);
END;
|
В следующем номере мы продолжим знакомство с языком описания аппаратуры AHDL на примере построения последовательностных схем и цифровых автоматов.
Тел.: 263 6736
E-mail: stesheenk@sm.bmstu.ru
|