Variable X:out real);
Она может быть вызвана со связыванием параметров:
variable sl,s2:natural:=12345; variable Random:real;
Uniform(X=> Random, SEEDl=>sl,SEED2=>s2);
или без связывания параметров:
uniform(sl,s2,Random);
Вызов процедуры без параметров - это просто вставленное имя ее. Но при этом имена фактических параметров должны совпадать с именами формальных параметров.
Возврат из процедуры или функции
Оператор возврата из подпрограммы - последовательный оператор return - немедленно прерывает выполнение процедуры или вызова функции и возвращает управление в программу, вызвавшую процедуру или функцию.
Операторы assert и report
Эти операторы были введены в язык VHDL для выявления ошибок моделирования и сообщения о них на консоль. У оператора ловушки assert следующий синтаксис:
\оператор assert\: := assert \6улевское выражение\ [report \строка сообщениях][severity \выражение\];
Здесь \6улевское выражение\ - проверка какого-либо условия правильности моделирования, которое равно false, если найдена ошибка и true, если моделирование верно; \строка сообщениях - выражение типа string, представляющее строку сообщения о причине ошибки. Например, оператор
assert a(i)='0'
report "бит " & integer'image(i) &" не равен О";
вызовет выдачу на экран дисплея сообщения:
-
: error: бит 1 не равен О
-
: Time: 2000 ns, Iteration: 0, TOP instance.
если а(1) * 0 на 2-й микросекунде моделирования. Здесь атрибут integer'image возвращает строку, в которой представлено значение целого i в строковом виде. Выражение \выражение\ имеет предопределенный тип severity_level, состоящий из элементов note, warn-
ing, error и failure. Значение выражения соответствует уровню критичности найденной ошибки и при самом высоком уровне failure моделирование останавливается. Например, если требуется остановить моделирование, можно записать оператор:
assert 1/=1 report "конец моделирования " severity failure;
Если не нужно следить за ошибкой, а только вывести сообщение о ходе моделирования, то применяют оператор сообщения с синтаксисом:
report \строка сообщениях [severity \выражение\];
Предыдущий пример можно переписать как:
report "конец моделирования " severity failure;
Оператор процесса
Оператор процесса - это параллельный оператор, представляющий основу языка VHDL. Его упрощенный синтаксис:
\оператор процесса\::=[postponed]
process [С\имя сигналах {Димя сигнала\})] [is]
{\объявление в процессе\}
begi n
{"^последовательный оператор\} end process;
Объявленными в процессе могут быть: объявление и тело подпрограммы, объявление типа и подтипа, объявление константы, переменной, файла, псевдонима, объявление и спецификация атрибута, объявление группы, описание use. To, что объявлено в процессе, имеет область действия (видимость), ограниченную данным процессом.
Все процессы в программе выполняются параллельно. Процессы обмениваются сигналами, которые выполняют синхронизацию процессов и переносят значения между ними. Нескольким присваиваниям одного сигнала в процессе соответствует один источник сигнала. Если над сигналами определена функция разрешения, то выходы источников сигнала могут объединяться. Сигналы нельзя объявлять в процессах. Процесс невозможно поместить в процесс, так как там есть место только для последовательных операторов.
В круглых скобках заголовка процесса указывается множество сигналов, по которым процесс запускается - список чувствительности. Такой оператор процесса эквивалентен процессу с оператором
wait on, стоящим последним в цепочке последовательных операторов тела процесса. В операторе процесса со списком чувствительности ставить операторы want не допускается.
Об отложенных процессах, отмеченных ключевым словом postponed, выше уже говорилось. Здесь следует добавить, что, так как отложенный процесс запускается последним в цепочке процессов с дельта-задержкой, то он сам должен исполняться с ненулевой задержкой.
Процесс представляет собой маленькую программу, которая выполняется на виртуальном процессорном элементе. Рассмотрим процесс, вычисляющий функцию синуса у от аргумента х по аппроксимирующей формуле:
sin(x) = С\Х + c2xs + с$х5 + сопроцесс выглядит следующим образом:
process
type tabl is аггау(0 to 3) of real;
constant с:tabl:-CO.99999, -0.16666, 0.00831, -0.00019); variable xtmp, p: real:=0.0; begin
xtmp:=X;
p:=c(0)*xtmp;
for i in 1 to 3 loop
p:=p+c(i)*xtmp*X*X; end loop;
у<=ф;
wait on X; end process;
Программа этого процесса содержит исходные данные (константы) и цепочку операторов, выполняемых последовательно. После пуска симулятора процесс начинает выполнение при определенном начальном значении сигнала X и останавливается на операторе wait. После прихода нового значения сигнала х процесс возобновляет свое исполнение до следующей остановки. При этом за один запуск процесса выполняются 10 операций умножения, 3 операции сложения, 5 операторов присваивания переменной и один оператор присваивания сигналу Y - результату программы процесса.
process(A) begin
с<= A or В; end process;
при моделировании дает графики,
как на рис. 20. Из графиков видно, что изменение результирующего сигнала С происходит только в моменты изменения сигнала А, т.е. процесс моделирует некоторую триггерную схему, а не схему ИЛИ.
Если в процессе запрограммирован алгоритм функционирования триггерной схемы, например, регистра, то допускается в списке чувствительности оставлять только сигналы управления записью и разрешения чтения, например, сигналы сброса и синхросерии.
При синтезе списки чувствительности игнорируются компилятором,
Процедуры и функции
Процедуры и функции относятся к числу подпрограмм. Выше уже упоминались вызовы процедуры и функции. В этом разделе рассмотрены правила составления и объявления процедур и функций.
Спецификация процедуры
Спецификация процедуры описывает действия при вызове процедуры и имеет следующий синтаксис:
\спецификация процедуры\::=procedure
\имя процедуры\[(\список параметров\)] is
{\объявление в подпрограмме\}
begin
^последовательный оператор\}
end [procedure][\имя процедуры\];
\список параметров\::=(\элемент списка\ {; \элемент списка\})
Хэлемент списка\:: = [constant|vari able|signal]
\идентификатор\{,\идентификатор\}: [i nI out|i nout] \тип параметрах [ := \статическое выражение\]
Здесь \имя процедуры\ - идентификатор процедуры. В списке параметров указывается информация о формальных параметрах процедуры. Вместо формальных параметров подставляются фактические параметры во время вызова процедуры. В каждом из элементов списка параметров может объявляться, какой это параметр (константа, переменная или сигнал), направление передачи параметра (i n, out или i nout), его тип или подтип и его значение по умолчанию, равное статическому выражению.
Список параметров может отсутствовать, т.е. он тогда задан неявно. При этом имена фактических параметров совпадают с именами формальных параметров, объявленных в процедуре. Обычно трудно понять программу с процедурами без списков параметров. Поэтому в соответствии с правилами повторного использования проектов, не рекомендуется программирование процедур без списка параметров.
Объявленными в процедуре могут быть: объявление и тело другой процедуры или функции, объявление типа и подтипа, объявление константы, переменной, файла, псевдонима, объявление и спецификация атрибута, объявление группы, описание use.
Рассмотрим пример программирования и использования процедуры в процессе для сортировки трех целых чисел.
process(al,a2,a3)
variable bl,b2,b3:integer;
procedure sort2(variable xl,x2:inout integer) is
variable t:integer; begin
if xl>x2 then
return; else
t:=xl; xl:=x2; x2:=t; end if; end procedure; begin
bl:*al; Ь2:=а2; ЬЗ:=аЗ; Sort2(b2,b3); Sort2(bl,b2); Sort2(b2,b3); cl<=bl; с2<=Ь2; c3<=b3; end process;
Здесь процедура Sort2 выполняет сортировку переменных xl,x2, выступающих формальными параметрами процедуры. Оператор return досрочно прерывает исполнение процедуры. Вызов процедуры выполняется с позиционным связыванием.
Следующий пример представляет использование процедуры со связыванием не всех формальных параметров.
procedure And4(variable xl,x2,x3,x4:in bit : = 'Г; signal y:out bit) is begin y<=xl and x2 and x3 and x4; end procedure;
При вызове этой процедуры:
And4(al,a2,a3,open,b);
четвертый входной параметр отсутствует, на что указывает ключевое слово open и вместо него подставляется начальное значение '1*.
При вызове процедуры входные параметры переменных копируются внутрь процедуры. После исполнения процедуры выходные параметры записываются на место соответствующих переменных.
Входные параметры сигналов представляются как ссылки на сигналы. Параметру сигнала нельзя присваивать начальное значение, так как источник этого сигнала недоступен. Поэтому нельзя также использовать такие атрибуты сигнала, как 'delayed,'stable,'quiet или 'transaction, которые опрашивают источник сигнала. Выходной параметр сигнала передается в процедуру вместе с источником сигнала, в котором происходит присваивание сигналу. Это эквивалентно тому, что цепочка последовательных операторов тела процедуры копируется в процесс на место вызова процедуры с соответствующей подстановкой параметров.
В отличие от вызова процедуры в обычных алгоритмических языках, в которых используется одно тело процедуры, в VHDL каждый вычислительный процесс, вызывающий процедуру, использует свой собственный экземпляр тела процедуры. И эти экземпляры исполняются параллельно, как и операторы процесса.
Спецификация функции
Спецификация функции имеет следующий синтаксис:
Хспецификация функции\::=[pure|impure] function \имя функции\ |\знак функции\ [(\список параметров\)] return \тип параметрах is {\объявление в подпрограме\} begin
^последовательный оператор\} return \выражение\; end [function][\имя функции\];
В ней знак функции - символ в кавычках, например, "+", список параметров - такой же, как в процедуре за исключением того, что
режимы параметров out и inout не допускаются. После ключевого слова return в объявлении функции указывается тип возвращаемого параметра. Объявления в функции могут быть такими же, как в процедуре. Выполнение функции должно оканчиваться оператором return, вычисляющим выражение возвращаемого параметра.
В отличие от процедуры, вызов функции возвращает только один параметр, зато он может участвовать как операнд в выражениях. В языке VHDL очень мало встроенных функций, т.е. операций. Большинство функций реализовано в виде подпрограмм. Для преобразования типов чаще всего используются функции. Например, функция
function Bit_To_int(x:bit_vector) return natural is variable t, j:integer:=0; begin
for I in x'reverse_range loop if ( x(i)='l') then
t:=t + 2**j; end if;
end loop; return t; end function Bit_To_int;
преобразует вектор битов в целое. При этом атрибут х'reverse_range возвращает диапазон, обратный диапазону представления входного параметра. Например, если входной параметр - bit_vector(7 downto 0), то в оператор цикла подставится диапазон 0 to 7. Таким образом, получается функция, универсальная для множества различных параметров-операндов.
Ключевые слова pure и impure обозначают идеальную и неидеальную функции. В отличие от идеальной функции, неидеальная функция может возвращать различные результаты для одинаковых наборов входных параметров. Например, если входной параметр -глобальная переменная, которая изменится в момент вызова функции, то результат будет отличаться от ожидаемого. Поэтому такие глобальные переменные не могут быть операндами в идеальных функциях.
Примером неидеальной функции является функция now из пакета standard, которая при вызове возвращает переменную предопределенного типа delay_length, равную текущему моменту времени моделирования. Естественно, что при различных вызовах этой функции она возвращает различные значения.
Объявление процедур и функций
Объявление подпрограмм вставляется в части объявлений процессов, блоков, в объявлениях объектов и пакетов, если эти подпрограммы используются в этих программных единицах. Оно представляет собой часть спецификации процедуры и функции до слова i s :
\объявление процедуры\::= procedure \имя процедуры\ [(\список параметров\)];
\объявление функции\::=[pure | impure] function \имя функции\ |\знак функции\[(\список параметров\)] return \тип параметрах;
Перегрузка процедур и функций
В языке VHDL допускается определять несколько процедур и функций с одинаковыми названиями. При вызове функции или процедуры из числа функций или процедур с одинаковым названием выбирается такая, которая подходит по типам и числу входных и выходных параметров. Такая функция загружается поверх остальных функций с таким же названием. Перегрузка (overloading) процедур и функций удобна при вычислениях с различными типами. Например, над типами integer, real, подтипами signed, unsigned определены одни и те же арифметические функции, но имеющие различное сочетание типов аргументов и результатов.
Примером перегрузки служит следующая функция, которая не входит ни в один стандартный пакет:
function "and"(xl,x2:bit) return boolean is begi n
return (xl and x2) ='1'; end;
Если эта функция объявлена, то теперь транслятор не выдаст сообщения об ошибке при выполнении оператора if, вызывающего эту функцию:
variable a,b,c: bit; if (a and b and c) then
* * ■
end if; так как выражение в скобках будет иметь правильный тип boolean.
Реализация процедур и функций в аппаратуре
Компиляторы-синтезаторы, как правило, транслируют процедуру или функцию в комбинационную схему. Процедуры с операторами wait не допускаются, например, такие, которые симулируют схему с памятью.
Атрибуты
Атрибутом называют особенное, долговременное свойство предмета. В языке VHDL сигналы, переменные и другие объекты, кроме своего значения, также имеют множество атрибутов. У каждого типа объектов есть несколько предопределенных атрибутов. Пользователь также может ввести ряд специальных атрибутов. Атрибуты бывают различного типа: атрибут - тип, значение, сигнал, функция, диапазон.
Атрибут объекта записывается как:
\имя объектах1 \имя атрибутах .
Ниже рассматриваются основные предопределенные атрибуты.
Атрибуты для скалярного типа
Для скалярного типа т предопределены следующие атрибуты:
т' left - самое левое значение множества элементов скалярного типа т. т' right - самое правое значение множества элементов типа т. T'high - наибольшее значение в множестве элементов типа т. Т' low - наименьшее значение в множества элементов скалярного типа т. T'image(x) - функция строкового представление выражения х типа т. T'value(x) - функция значения типа т от строкового представления х. T'pos(x) - функция номера позиции элемента х типа т. T'val (х) - функция значения элемента типа т стоящего в позиции X.
Примеры атрибутов:
type st is (one,two,three);
st'right = three, st'pos(three) = 2, st'val(l) = two. positive'low * 1, positive'high =2147483647. integer'valueC'LOOO") =1000, integer'image(330) ="330".
Атрибуты для регулярного типа
Для регулярного типа а предопределены следующие атрибуты:
A'left[(N)] - левое значение диапазона индексов по N-й размерности.
A' right[(n)] - правое значение диапазона индексов по N-й размерности.
Afhigh[(N)] - наибольшее значение диапазона индексов по N-й размерности.
А' 1 cw [ (n) ] - наименьшее значение диапазона индексов по N-й размерности.
A' range[(N)] - диапазон индексов по N-й размерности.
A' reverse_range[(N)] - обратный диапазон индексов по N-й размерности.
A' length[(N)] - протяженность диапазона индексов по N-й размерности.
A'ascending[(N)] - функция, равная true, если диапазон индексов He-N-й размерности - возрастающий.
Примеры применения атрибутов:
type s2 is array(2 downto 1, 0 to 3) of integer;
S2'left(l) - 2, s2'right(2) - 3,
S2'high(l) * 2, s2'low(2) = 0,
s2'range(2) = 0 to 3, s2'reverse_range(l)- 1 to 2,
S2'length(2) = 4.
Атрибуты сигналов
Атрибуты сигналов s:
S' stable [CO] - сигнал, равный true, если за промежуток времени т не было событий у сигнала S.
S'transaction - сигнал типа bit, меняет значение на противоположное в циклах моделирования, в которых было присваивание нового значение сигналу S.
s'event - сигнал, равный true, если произошло событие в сигнале S в данном цикле моделирования.
sfactive - сигнал, равный true, если произошло присваивание нового значение сигналу s в данном цикле моделирования.
S'last_value - сигнал такого же типа, что и S, содержащий значение s до последнего события в нем,
Примером применения атрибутов сигналов является следующий процесс, моделирующий синхронные триггеры.
process(CLK) begin
if CLK='l' and CLK'event then-- D- триггер
ql<=al; end if; if not CLK'stable then -- D- триггер
q2<=a2; end if; if CLK'last_value /= CLK then-- D- триггер
q3<=a3; end if; if CLK'active -- D- триггер
q4<=a4; end if; q5<=CLK'transaction; — т- триггер
end process;
Атрибуты пользователя
Эти атрибуты предназначены для присваивания объектам языка дополнительных свойств, не предусмотренных встроенными типами и атрибутами. При проектировании дискретных устройств такими свойствами могут быть: способ кодирования состояний автомата, указания компилятору-синтезатору по управлению оптимизацией, размещению блоков, их исполнению, назначение портов номерам выводов, начальное состояние схем памяти и т.п. Т.е. эти свойства не связаны напрямую с алгоритмом, реализуемым в программе. Задание атрибута состоит из его объявления и спецификации.
Объявление атрибута имеет синтаксис, похожий на объявление переменной:
\объявление атрибутах::=attribute \идентификатор\: \ тип\
где \тип\ - любой ранее определенный тип, например, string, positive, time.
Спецификация атрибута имеет синтаксис:
Хспецификация атрибутах::=attribute \идентификатор\ of \имя объектах[{Димя объекта\}] | others I all : \класс объекта\ is \выражение\ \имя объекта\::= ((\идентификатор\ |\символьный литерал\
| \символ оператора\) [\сигнатура\]) \класс объекта\::- entity|architecture|configuration| package I procedure|function[type 1 subtype[ constant|si gnal jvari able | f i 1 e |component| label (literal |units I group
Здесь \идентификатор\ - имя атрибута, объявленного ранее, \имя объекта\ - имя объекта, которому присоединен атрибут,\сигнатура\ -
список типов параметров, если объектом является процедура, функция или перечисляемый литерал, предназначенный для идентификации перегружаемых процедур, функций и литералов. Спецификация атрибута обычно вставляется сразу же после объявления этого атрибута.
Атрибуты пользователя обычно не влияют на поведение модели и такие атрибуты игнорируются симулятором. Но они имеют большое значение для компилятора-синтезатора и для средств реализации схемы в кристалле. Поэтому, как правило, фирмы-поставщики компиляторов и производители микросхем предлагают для использования свои наборы атрибутов пользователя. В следующем примере атрибут указывает способ кодирования состояний автомата:
type \состояние\ is ( \сброс\ Дначало\,\работа\,\конеи.\); attribute enum_encoding : string;
attribute enunuencoding of \состояние\: type is "000 001 010 100" ;
Атрибут foreign
Специальный атрибут foreign присоединяется к архитектуре, процедуре или функции. Он указывает симулятору, что соответствующий объект должен моделироваться особенным способом. Это может быть специальная программа на ассемблере или другом языке, или аппаратный ускоритель.
Структура программы на VHDL
Дискретная система может быть представлена в VHDL как объект проекта. Объект - это основная составная часть проекта. Объект может быть использован в другом проекте, который, в свою очередь, описан как объект или может являться объектом более высокого уровня в данном проекте. Симулятор может моделировать ВУ, только если оно оформлено в виде объекта проекта.
Объект проекта описывается набором составных частей проекта, таких как: объявление объекта, тело архитектуры объекта (или просто архитектура), объявление пакета, тело пакета и объявление конфигурации. Каждая из составных частей объекта может быть скомпилирована отдельно. Составные части проекта сохраняются в одном или нескольких текстовых файлах с расширением .VHD. В одном файле может сохраняться несколько объектов проекта.
Объект проекта обычно описывается согласно синтаксису:
\объект проекта\::= [\описание 1ibrary\]
[\описание use\]
\объявление объекта\
\тело архитектуры\
[\объявление конфигурации\] [\описание library\]::= library \идентификатор\ {, \идентификатор\};
где идентификаторы - названия библиотек, которые используются в объекте проекта. Они указывают транслятору местоположение этих библиотек. Описание use указывает, какие пакеты и какие элементы этих пакетов могут быть использованы в объекте. Части объекта проекта могут компилироваться раздельно. Но перед связыванием (elaboration) все части должны быть уже скомпилированы.
Объявление объекта
Объявление объекта указывает, как объект проекта выглядит снаружи и каким образом его можно включить в другом объекте проекта в качестве компонента, т.е. он описывает внешний интерфейс объекта. Синтаксис объявления объекта:
\объявление объекта\::= entity \идентификатор\ is
[депеп"с(\объявление настроечной константы\
{; \объявление настроечной константы\});] [port (\объявление порта\ {Добъявление порта\});]
{\объявление в объекте\} [begin
{\оператор assert\ | \пассивный вызов процедуры\
| \пассивный процесс\ }] end [entity][\идентификатор\];
Здесь \идентификатор\ - имя объекта. Синтаксис объявления настроечной константы и объявления порта рассмотрены в разделе описания объектов и типов языка. Порты представляют собой интерфейсные сигналы объекта проекта. Настроечные константы generic кодируют определенные свойства объекта проекта, например, разрядность линий связи, параметры задержки, кодирование структуры моделируемого устройства.
Объявленными в объекте могут быть: объявление и тело процедуры или функции, объявление типа и подтипа, объявление глобальной переменной, файла, псевдонима, константы, объявление и спецификация атрибута, объявление группы, описание use.
В исполнительной части, которая открывается словом begin, вставляются параллельные операторы, которые не выполняют присваиваний сигналам, т.е. не влияют на поведение объекта. Поэтому
такие вызовы процедуры и процессы называются пассивными. Наиболее частое применение этих операторов - проверка соответствия входных сигналов, поступающих через порты, заданным требованиям или соответствие включения объекта в окружение, задаваемое ограничениями на настроечные константы generic. Например, проверяется время предустановки сигнала относительно фронта синхро-серии, соответствие его уровней, разрядность входных данных и т.п. При несоответствии сигналов или настроечных констант, оператор assert выдает сообщение об ошибке.
Рассмотрим пример объявления объекта RS-триггера:
entity RS_FF is
generic(delay:time); port(R, S: in bit;
Q: out bit:='0'; nQ: out bit:='l'); begin
assert (R and S) /='1' report" in rs_ff r=s=1" severity error; end entity RS_ff;
В нем настроечная константа delay задает параметр задержки, например, от входа до выхода, который будет подставлен при компиляции на этапе связывания компонентов. Порты R,S имеют режим ввода in, а порты Q,nQ - режим вывода out. При единичных сигналах на обоих входах, т.е. когда RS-тригтер функционирует неправильно, оператор assert выдает сообщение об ошибке.
Архитектура объекта
Архитектура объекта представляет собой отдельную часть, в которой описано, каким образом реализован объект. Ее синтаксис:
\тело архитектуры\::= architecture \идентификатор\ of \имя объекта\ is
{\объявление в 6локе\} begin
{ \параллельный оператор\} end [architecture][\идентификатор\];
Идентификатором обозначается имя конкретного тела архитектуры. Одному объекту проекта может соответствовать несколько архитектур, в каждой из которых описан один из вариантов реализации объекта. Поэтому имя объекта указывает, который из объектов описан в этом теле архитектуры. Объявление в теле архитектуры такое же, как в операторе блока, который описан ниже. Им может быть: объявление и тело процедуры или функции, объявление типа и подтипа, объявление файла, псевдонима, константы, глобальной переменной, объявление и спецификация атрибута, объявление группы,
описание use, а также объявление компонента. Объявленные в теле архитектуры типы, сигналы, подпрограммы видимы только в пределах этой архитектуры.
Исполнительную часть архитектуры составляют параллельные операторы, такие как процесс, блок, параллельное присваивание сигналу и др. Так как все операторы в исполнительной части тела архитектуры - параллельные, их взаимный порядок - безразличен. Хорошим стилем считается, когда параллельные операторы ставятся в последовательности, соответствующей нумерации вершин граф-схемы алгоритма, реализуемого в архитектуре.
Примером тела архитектуры служит архитектура для вышеприведенного объявления объекта RS-триггера:
architecture behav of rs_ff is begin
processes,R)
variable qi: bit; begin
if S^'l' then
qi:='l' ; elsif R='l' then
qi:='0'; end if;
Q<=qi after delay; nQ<=not qi after delay; end process; end architecture behav;
Данную программу, включающую объявление объекта и тело архитектуры можно транслировать и моделировать. Если из объявления объекта удалить исполнительную часть и описание generic, то программа будет написана синтезируемым стилем и ее можно также компилировать в логическую схему.
Объявление компонента
Объявление компонента в теле архитектуры необходимо, если в архитектуре используется оператор вставки этого компонента, который будет рассмотрен ниже. Синтаксис объявления компонента следующий :
\объявление компонентах::= component \идентификатор\ [is] [депеп'с(\объявление настроечной константы\
{; \объявление настроечной константы\});] [port (\объявление порта\ {Добъявление порта\});] end component [\идентификатор\];
Объявление пакета
В пакет обычно объединяются декларации различных объектов и типов языка, связанных общим признаком. Затем декларации из пакета можно повторно использовать в различных частях проектов, ссылаясь на этот пакет. Многие пакеты стандартизированы и их использование упрощает разработку новых проектов, а также служит для стандартизации включения и тестирования этих проектов. Несколько пакетов, подчиненных одной цели, собирают в библиотеку library. Библиотека, в которой собраны программы и пакеты пользователя, по умолчанию имеет название WORK.
Синтаксис объявления пакета:
\объявление пакета\::= package \идентификатор\ is
{объявление в пакете} end [package][\идентификатор\];
В объявлении пакета могут быть объявленными объявление процедуры или функции, объявление типа и подтипа, объявление файла, псевдонима, константы, глобальной переменной, объявление и спецификация атрибута, объявление компонента, объявление группы, описание use.
Обычно в объявлении пакета объявляются типы, используемые во всех объектах проекта или ряда проектов. Если объявлены процедуры и функции, то их спецификации описываются в теле пакета. Т.е. объявление пакета представляет собой интерфейс пакета, также как объявление объекта - это интерфейс объекта проекта.
Константам может быть не присвоено значение. Такие константы называются отложенными (deferred). Например, это могут быть заранее неопределенная кодировка состояний или разрядность шин. Тогда эти константы должны получить значение в теле пакета.
При обращении к объектам с одинаковым именем, принадлежащим разным библиотекам, необходимо использовать селективное имя объекта, указывающее, какой библиотеке оно принадлежит, например:
signal my_bit: IEEE.std_logic_1164.x0lZ;
Тело пакета
Тело пакета необходимо приводить в паре с объявлением пакета, если в последнем объявлены подпрограммы или отложенные константы. Обычно объявление и тело пакета оформляют в одном файле. Тело пакета имеет синтаксис:
\тело пакета\::= package body \идентификатор\ is
{объявление в теле пакета} end [package body][\идентификатор\];
В объявлениях тела пакета должны быть приведены полные спецификации процедур и функций, присваивания константам, которые задекларированы в объявлении этого пакета.
Ниже приведен пример пакета функций и констант, который удобно применять для упрощения программирования логики с булевскими и битовыми типами.
package short_boolean is
constant bO:boolean:=false; —псевдоним для false constant bl:boolean:=true; --псевдоним для true function b(x:bit) return boolean;— преобразование из bit в boolean function "not"(x:bit) return boo7ean; —функции вместо not, and, or function "and"(xl,x2:bit) return boolean ; function Mor"(xl,x2:bit) return boolean ; end package; package body short_boolean is
function b(x:bit) return boolean is begin
return x -'1'; end;
function "not"(x:bit) return boo7ean is begin
return x ^'O1; end;
function ,,and"(xl,x2:bit) return boolean is begin
return (xl and x2) ='1'; end;
function Mor"(xl,x2:bit) return boolean is begi n
return (xl or x2) ='l'', end; end package body;
Теперь, если данный пакет присоединить к объекту проекта с помощью описаний library и use, то подстановка констант и функций этого пакета во многих местах, например, в операторах if, when сокращает запись программы.
Описание use
Как уже отмечалось, объекты языка, объявленные в подпрограмме, процессе, пакете и объекте проекта видимы только в границах этих структурных единиц программы. Для того чтобы в данной
структурной единице был видимый объект, объявленный в другом месте, используется описание use. Его синтаксис:
\описание use\::= use \селективное имя\ {, \селективное имя\ };
\селективное имя\::= \имя1\. \имя2\
\имя2\::= \идентификатор\ | \символьный литерал\ |аП
Здесь \имя1\ представляет собой обозначение места, где находится объект, который должен быть видимым. Чаще всего это идентификатор библиотеки и идентификатор пакета в ней, разделенные точкой. Идентификатор - название объекта, который должен быть видимым, символьный литерал - символьное имя функции, например, "*". Ключевое слово all означает, что видимы все объекты, объявленные в указанном месте. Например, чтобы были видимы функции сложения и вычитания из пакета std_7ogic_arith библиотеки IEEE используют описание:
use IEEE.std_logic_arith.,,-n, iEEE.std_logic_arith."+" ;
а если все объявления из этого пакета должны быть видимыми то используют use IEEE.std_logic_arith.all;
Псевдонимы
Под псевдонимом в языках программирования понимают другое имя объекта. Псевдонимы в VHDL помогают представить программу в виде, более удобном для чтения и моделирования. Объявление псевдонима имеет синтаксис:
\объявление псевдонимах::= alias \идентификатор\ |
символьный литерал\ | \символ оператора\ [:\подтип\] is \имя\ [\сигнатура\];
Наиболее часто псевдоним дают константам, переменным и сигналам. Например, псевдоним
alias COP: bit_vector(7 downto 0) is INSTRUCTION(31 downto 24);
помогает обращаться с полем кода операции сор команды instruction как с отдельным сигналом, не объявляя этот сигнал. При этом бит сор(7) равен биту instruction(31) .
j. акуке псевдоним можно присваивать типу. Например, если дать псевдоним
alias vect is std_logic_vector;
то можно сократить текст программы, объявляя сигнал типа vect вместо std_logic_vector, правда, при этом ухудшится чтение её другими программистами.
Можно давать псевдоним функциям и процедурам. Например:
alias to_v is conv_std_logic_vector
[integer, integer return std_logic_vector];
где сигнатура в квадратных скобках указывает, для какой из перегружаемых функций, различаемых по набору параметров, сделать псевдоним.
Часто псевдоним дают длинным селективным именам, которые обозначают объекты, принадлежащие различным библиотекам, как например:
alias PI is IEEE.math_real.math_pi ;
- это псевдоним константы числа пи.
Псевдонимы поддерживаются многими компиляторами - синтезаторами, но не в полном объеме. Не поддерживаются, например, псевдонимы процедур и функций.
Метки в программе
Выше отмечалось, что операторы цикла могут иметь метку, которая используется для организации вложенных циклов. В языке VHDL любой оператор может иметь метку. Метка - это идентификатор, уникальный в пределах данной программной единицы, который отделен от оператора двоеточием. Рекомендуется применять метки с большинством операторов в программе, что объясняется следующими причинами.
В операторе процесса, блока и др. встречаются объявления констант, процедур и функций, которые видимы в пределах данного оператора. Чтоб они были видимы в другом месте, можно использовать описание use, в котором, как адрес объявления, указывается метка процесса.
Операторы, такие как процесс, могут занимать большое количество строк текста. Тогда метка, поставленная в конце оператора, совпадающая с меткой вначале, служит закрывающей скобкой, по которой программист быстро найдет границу оператора.
Такие операторы, как вставка компонента и блок, обязательно имеют метку. По этой метке с помощью объявления конфигурации можно подставить нужную реализацию компонента или блока, отличную от реализации, заданной в архитектуре проекта.
При моделировании симулятор работает со скомпилированной программой, в которой всем параллельным операторам поставлены
метки. Если у оператора не было метки, то симулятор ставит метку по своему усмотрению, например, номер строки. По этой метке программист ищет в модели переменные и сигналы, изменяемые в операторе. Поэтому удобнее это делать по метке, название которой по смыслу указывает на место в программе.
При трансляции программы в логическую схему компилятор-синтезатор каждому параллельному оператору ставит в соответствие логическую схему или библиотечный компонент с именем, соответствующим метке оператора. Если метка отсутствует, то компилятор ставит произвольное имя, например, букву с порядковым номером. Такое имя затрудняет дальнейшие отладку и тестирование проекта на уровне логической схемы и схемы после размещения и трассировки.
Имя метки может отражать специфику и алгоритм функционирования данного оператора. Например, процесс, моделирующий регистр - аккумулятор, может обозначаться меткой RG_ACC. В этом случае другому программисту легче разобраться в программе. Поэтому вставка меток операторов это обязательное требование для проектов, предназначенных для повторного использования.
Объявление конфигурации
После того, как проект описан в виде объекта и его архитектуры, которая включает в себя различные компоненты, эти компоненты можно заменить на другие с таким же интерфейсом, применив объявление конфигурации. Его упрощенный синтаксис:
\объявпение конфигурации::=
configuration \идентификатор\ of \имя объекта\ is for \имя архитектуры\
{for Хуказатели вставки компонентах: \имя компонентах Хуказатель связываниях; end for;} end for; end [configuration] [\идентификатор\];
\ указатели вставки компонента \::=\метка вставки компонентах
{Дметка вставки компонентах} | others | all Хуказатель связываниях::= use entity
\имя объектах [(^идентификатор архитектуры\)]
При разработке вычислительного устройства его обычно тестируют на различных этапах проектирования с помощью одного и того же испытательного стенда (test bench), который также описан с помощью пары объект-архитектура, например, alu_tb(tb_arch). В этом случае вставленный в испытательный стенд компонент тести-
руемого объекта, например, ALU с меткой иит (unit under test), имеет различное исполнение, т.е. архитектуру, например, RTL. Для того, чтобы не изменять описание архитектуры испытательного стенда, изменяют только его конфигурацию, например:
configuration testbench_for_alu of alu_tb is
for TB_ARCH
for UUT: ALU
use entity work.ALU(RTL); end for; end for;
end TESTBENCH_FOR_ALU;
Расширенный синтаксис объявления конфигурации предполагает такие возможности, как подключение других конфигураций, вставку компонента непосредственно в конфигурации (в указателе связывания), связывание настроечных констант компонента с новыми значениями, отключение компонента от схемы (отложенное включение, когда в указателе связывания - ключевые слова use open).
Если в конфигурации используются компоненты, описанные в другой библиотеке, то их делают видимыми с помощью описаний library и use, которые ставят перед конфигурацией.
Конфигурации обычно игнорируются компиляторами-синтезаторами.
Параллельные операторы
VHDL - это язык параллельного программирования. Параллелизм в программе задается явным образом в параллельных операторах. Параллельным операторам соответствуют виртуальные процессорные элементы в программистской модели вычислителя VHDL. Параллельные операторы образуют исполнительную часть тела архитектуры. Выше был описан оператор процесса, как основной параллельный оператор, а также упоминался оператор вставки компонента. В этом разделе рассмотрены эти и другие параллельные операторы.
Все параллельные операторы, кроме оператора блока и вставки компонента, могут быть заменены на эквивалентные операторы процесса, в которых список чувствительности содержит все входные сигналы выражений исходного оператора и у которых оператор wait стоит последним в цепочке последовательных операторов.
Оператор параллельного присваивания
Этот оператор имеет такой же синтаксис, как и оператор присваивания сигналу в процессе. Такой оператор эквивалентен оператору процесса, в котором этот оператор повторен в его исполнительной части, а последним оператором стоит оператор wait со списком чувствительности. Например, следующие два оператора эквивалентны:
ADDER_D:A<=B+C;
adder_p:process begin А<=в+С; wait on в,С; end process;
Оператор условного параллельного присваивания
Оператор условного параллельного присваивания имеет синтаксис:
\условное параллельное присваивание\::= \имя\<= [\способ задержки\]
{\график\ when \6улевское выражение\ else } \график\|>пеп \6улевское выражение\];
где определение способа задержки и графика представлено выше при описании оператора присваивания сигналу.
Любой оператор условного параллельного присваивания имеет эквивалентное представление в виде процесса, как например, оператор:
cntrl<= one when st=l else
two when st=2 or st=3 else three;
эквивалентен оператору
process(st,one,two,three) begin
if st=l then
cntrl<= one; elsif st=2 or st=3 then
cntrl<= two; else
cntrl<=three; end if; end process;
Оператор селективного параллельного присваивания
Оператор селективного параллельного присваивания имеет синтаксис:
\селективное параллельное присваивание\::= with \выражение\ select {\имя\<= [\способ задержки\]{\график\ when \альтернативы\,}
\rpa0MK\[when others ];
где \альтернативы\ имеют то же значение, что и в операторе case. Этот оператор эквивалентен соответствующему процессу, как например, оператор:
with st select
cntrl<= one when 1,
two when 2 to 3, three when others;
выполняет такие же действия, что и процесс:
process(st,one,two,three) begin
case st is
when 1 => cntrl<= one;
when 2 to 3 => cntrl<= two; when others => cntrl<= three,-end case; end process;
Требования к выражению селектора и альтернативам оператора такие же, как у оператора case. Так, каждая из альтернатив должна быть такого же типа, что и \выражение\ и представлена статическим выражением или диапазоном. Никакие два значения, получаемые из выражений альтернатив, не должны быть равны друг другу.
Параллельный оператор assert
Этот оператор имеет такой же синтаксис, как и соответствующий ему последовательный оператор. Он выполняется точно также, как и процесс, в исполнительной части которого стоит последовательный оператор assert с таким же содержанием.
Параллельный вызов процедуры
Параллельный вызов процедуры имеет такой же синтаксис, как у последовательного вызова процедуры. Он исполняется точно так же, как процесс, который имеет в своей исполнительной части такой же вызов процедуры с такими же параметрами и оператор wait ожидания прихода сигналов - входных параметров. Поэтому каждый параллельный вызов процедуры соответствует некоторому виртуальному процессорному элементу, исполняющему алгоритм этой процедуры.
Если программа предназначена для синтеза, то процедура, вызываемая параллельно, не должна иметь операторов wait. Такая процедура отображается в комбинационную схему или комбинацию шин, т.е. некоторый специализированный процессорный элемент.
Отложенные параллельные операторы
Так как всем вышеперечисленным параллельным операторам ставится в соответствие оператор процесса, то поскольку процесс бывает отложенным, то и эти параллельные операторы могут быть отложенными. Такие операторы, как и отложенный процесс, обозначаются ключевым словом postponed, которое ставится перед оператором.
Оператор вставки компонента
Оператор вставки компонента играет важную роль для реализации иерархического проектирования. Его синтаксис:
\оператор вставки компонентах::= \метка экземпляра элементах :
Хвставляемый элемент\ [generic тар(\связывание настроечной константы\ {, \связывание настроечной константы\});] [port map (Дсвязывание порта\ {Дсвязывание порта\})]; Хвставляемый элемент\ ::= [component] \имя компонентах
I entity \имя объектах[идентификатор архитектуры] (configuration \имя конфигурации\
Действие этого оператора заключается в подстановке вместо себя одного экземпляра компонента (вставляемый элемент - компонент) или объекта (вставляемый элемент - объект проекта), или компонента, указанного в конфигурации. Если вставляется компонент, то он должен быть объявлен в данной архитектуре.
Сколько раз встречается имя вставляемого компонента - столько копий объекта вставляется в странслированную программу. При этом каждая копия имеет уникальное имя, указанное в метке компонента. Таким образом, при многократной вставке компонента в странслированном проекте дублируются несколько раз все параллельные операторы указанного компонента, а следовательно, дублируются сответствующие им виртуальные процессорные элементы программистской модели.
Фраза \связывание порта\ указывает порядок подключения сигналов данного тела архитектуры к портам - сигналам компонента. Связывание может быть как позиционным, так и ассоциативным (поименованным), а также комбинированным. При позиционном
связывании параметры-выражения подставляются в порядке, определенном порядком следования имен параметров в объявлении компонента. При поименованном связывании каждое имя порта связывается с соответствующим сигналом с помощью символов "=>", причем порядок следования портов может быть произвольным. Второй способ связывания предпочтителен, так как поименованное связывание более понятно при чтении и меньше вероятность допустить ошибку при записи.
Если на момент связывания, объект, выступающий компонентом не скомпилирован или в указанных в фразе use библиотеках не нашлось объекта с подходящим названием и интерфейсом, то такой компонент не будет вставлен. Об этом транслятор даст предупреждение. При моделировании такого устройства, на выходы неподключенного компонента симулятор подает сигналы 'и' - не инициализировано. А при синтезе - компилятор представит его "черным ящиком" (black box), который он попытается заменить схемой из EDIF-файла с соответствующим названием, если такой имеется в рабочем каталоге.
Неподключенные порты с режимом out или inout допускается не указывать в списке связывания портов. Но более понятным и правильным считается связывание таких портов с условным сигналом, обозначенным ключевым словом open.
В поименованном связывании допускается связывать вырезку порта с сигналом. Например, вставка компонента регистра, выходы которого подключены к различным сигналам, имеет вид:
u_rg: RG16 port map (clk=>clk,
E => \разр_зп_рг\,
DI => D,
D0(15)=> open,
D0(14)=> \знак_й\,
D0(13 downto 0)=>\мантисса_й\ );
Более полный синтаксис допускает связывать порт и сигнал в виде статического выражения с использованием вызовов функций, например:
Dl => conv_integer(D(n-l downto 0)); To_Bit(DO(14)) => \знак_0\,
т.е. здесь для совмещения типов порта DI в режиме in и сигнала D, разряда DO(14) порта в режиме out и сигнала \знак_й\ используются функции преобразования типа.
Следует отметить, что компиляторы-синтезаторы, как правило, не допускают связывание порта с функцией преобразования типа. Если выход компонента не подключен, как, например, DO(15)=>openf или если выхода нет в списке связываний, то компилятор-синтезатор может минимизировать в компоненте цепи и логику, относящиеся к этому выходу, в примере - удалить триггер, подключенный к D0(15).
Синтаксис связывания настроечной константы аналогичен синтаксису связывания порта. При таком связывании настроечная константа, обозначающая, например, разрядность шин, объем памяти, задержку элемента, передается из объекта проекта более высокого уровня в объект более низкого уровня, который становится компонентом. Другими словами, при связывании настроечных констант выполняется настройка в некотором смысле обобщенного объекта до компонента с конкретными параметрами.
Все компиляторы-синтезаторы поддерживают настроечные константы целого типа.
Пусть на нижнем уровне иерархии описана модель синхронного регистра разрядности п, который принимает данное DI по фронту синхросигнала CLK:
entity RGn is
generi c(n: i nteger);
port(CLK:bit; DI, DO: bit_vector(n-l downto 0)); end entity;
architecture behav of RGn is begin process(CLK) begi n
if clk='1' and CLK'event then
DO<=Dl ; end process; end architecture behav;
Тогда следующий оператор в теле архитектуры более высокого уровня иерархии выполняет вставку компонента этого регистра и настраивает его разрядность равной п = 8:
U_RG8: entity RGn(behav) generic map(8),
port map (CLK,DI=>DATA_IN,DO=>DATA_OUT);
Оператор generate
Если необходимо неоднократно повторить один или несколько параллельных операторов, то используют оператор generate. Его синтаксис:
\оператор generate\::= \метка\: for \идентификатор\ in \диапазон\ generate
[{\объявление в 6локе\} begin]
{ \параллельный оператор\} end generate [\метка\];
Метка оператора generate необходима для обозначения сгенерированной структуры, \идентификатор\ - это параметр оператора generate, а фраза \диапазон\ - диапазон его изменения. Они имеют такие же синтаксис и семантику, как и в операторе loop. В операторе могут быть вставлены такие же объявления, как в декларативной части тела архитектуры.
В отличие от оператора loop, который повторяет в цикле один или несколько последовательных операторов, оператор generate делает несколько копий параллельных операторов, когда параметр оператора пробегает все значения из заданного диапазона.
В следующем примере с помощью оператора generate запрограммирована схема сдвигового регистра длиной п на триггерах FD из библиотеки компонентов ПЛИС Xilinx, описанного в пакете UNlslM.umsim_viTAL с входом DI и выходом do, тактируемого син-хросерией clk.
signal t: std_logic_vector(l to n+1);
t(i)<=DI;
FIFO: for i in 1 to n generate
u_ tt: fd(c=>clk, D=>t(i), Q=>t(i+1)); end generate; D0<=t(n+1);
Для того, чтобы управлять структурой проектируемого устройства используется условный оператор generate. Его синтаксис:
Хусловный оператор generate\::=\MeTKa\:if \6улевское выражение\ generate
[ {\объявление в 6локе\} begin]
{ \параллельный оператор\} end generate [\метка\];
В зависимости от условия, заданного булевским выражением, оператор вставляет или нет в структуру устройства узлы, представленные параллельными операторами. Так как это булевское выражение влияет на структуру устройства, оно должно быть статическим. В примере:
RESn: if \подключить_Р1Л_1_иР\=1 generate
RESl:for i in DATA^BUS1range generate
U_ RES: PULLUP(DATA_BUS(i));
end generate; end generate;
если целое значение \подключить_Р1Л_1_иР\ равно 1, то к шине DATA_BUS подключаются компоненты нагрузочных резисторов PULLUP из библиотеки unisim.
Оператор block
В языке VHDL блок представляет собой выделенное множество параллельных операторов. Его синтаксис:
\оператор block\::=[\метка\]: block [\выражение сдерживаниях] [is] [депепс(\объявление настроечной константы\
{; \объявление настроечной константы\});] [generic тар(\связывание настроечной константы\
{, \связывание настроечной константы\});] [port (\объявление порта\ {;\объявление порта\});] [port map (\связывание порта\
{Дсвязывание порта\})]; {\объявление в 6локе\} begin
{\параллельный оператор\ } end block [\метка\];
В описании блока фраза generic объявляет типы внутренних настроечных констант блока, фраза generic map, описывает список связывания настроечных констант, поступающих извне с внутренними настроечными константами, объявления порта описывают входные и выходные сигналы блока, а список связываний портов задает соответствие внешних сигналов и сигналов портов. В блоке могут быть объявлены те же объекты языка, которые объявляются в теле архитектуры.
Отдельное необязательное булевское выражение сдерживания неявно задает специальный необъявляемый сигнал guard, который разрешает или запрещает (сдерживает) выполнение операторов присваивания сигналу с условием guard. Этот же сигнал может участвовать как операнд в выражениях в пределах блока.
Как и в других языках программирования, блоки в VHDL выполняют две основные функции: создание локальной памяти для сигналов и введение обособленной области их действия (области видимости). Также блоки могут иметь иерархическое построение, т.е. могут применяться в блоках на более высоком уровне. С помощью оператора конфигурации можно вместо одних блоков подставлять
блоки с другим исполнением. Эквивалентным блоком можно заменить любую пару объект-архитектура, используемую как компонент.
В разделах, посвященных операторам процесса и присваивания сигналу указывалось, что нельзя одному сигналу присваивать значение в разных процессах, если над типом сигнала не определена функция разрешения. Для реализации корректного присваивания сигналу любого типа в разных процессах, язык VHDL предоставляет операторы присваивания сигналу со сдерживанием.
Для этого, во-первых, процесс с оператором присваивания сигналу или соответствующий параллельный оператор помещают в блок с выражением сдерживания. Во-вторых, оператор присваивания сигналу оформляют как оператор со сдерживанием, для чего перед выражением оператора ставится ключевое слово guarded. Например, блоки В1 и В2:
signal А,В,С: out integer bus :=0; disconnect c:integer after 2 ns;
Bl: block (sel = 1) is begin
с <= guarded A;