2.5.1 Общие сведения
VHDL является строго типизированным языком. Это означает, что он требует согласования типов объектов, используемых в выражениях – например, не допускается присваивать переменной или сигналу одного типа значение, относящееся к другому типу, даже если есть возможность привести один тип к другому. В случае необходимости разработчик должен явно указать функцию преобразования типа, показывая тем самым, что он понимает суть выполняемых действий и согласен, что тип выражения должен измениться. Строгая типизация является одним из элементов повышения общей надежности разрабатываемых проектов.
Типы данных в VHDL делятся на следующие категории:
- скалярные;
- композитные;
- указатели;
- файлы.
2.5.2 Скалярные типы
Тип bit служит для представления величин, принимающих значения 0 и 1. Его использование в целом не рекомендуется, поскольку данного набора недостаточно для описания цифровых линий, которые могут находиться, например, в состоянии высокого импеданса.
Тип boolean служит для представления логических величин, принимающих значения ИСТИНА и ЛОЖЬ (true/false). Такой тип данных является результатом операций сравнения, например, выражение A>B представляет собой величину типа boolean.
Основным типом, наиболее адекватно представляющим цифровые сигналы, является std_logic. Он может принимать следующие значения:
‘U’ – не инициализировано;
‘X’ – неизвестное значение, формируемое активным выходом (forcingunknown);
‘0’ – логический ноль;
‘1’ – логическая единица;
‘Z’ – «третье состояние», состояние высокого импеданса выхода;
‘W’ – «слабое неизвестное» значение;
‘L’ – «слабый ноль», состояние логического нуля, формируемое источником сигнала с более высоким сопротивлением, чем обычный логический выход;
‘H’ – «слабая единица», состояние логической единицы, формируемое источником сигнала с более высоким сопротивлением, чем обычный логический выход;
‘-‘ – произвольное значение (don’tcare).
Такой список возможных значений учитывает не только особенности аппаратной реализации цифровых устройств, но и особенности их моделирования. Например, состояния «неизвестное значение», «слабое неизвестное значение» в означают, что программа моделирования не в состоянии определить логический уровень, который должен присутствовать в данном узле схемы (однако в реальном устройстве он, конечно же, будет равен какому-то значению). Аналогично, состояние «не инициализировано» говорит только о том, что начальное состояние не было сообщено программе моделирования, тогда как в цифровой системе в аналогичной ситуации каждый элемент будет иметь вполне определенное состояние.
Понятия «слабых» (weak) сигналов соответствуют источникам логических уровней с повышенным сопротивлением по сравнению с обычными логическими элементами. Объединение обычного и слабого источников не является схемной ошибкой, и в этом случае состояние определяется более сильным источником. Например, соединение входа с одним из источников питания через резистор (pull-up или pull-down) обеспечивает на этом входе логический уровень (1 или 0 соответственно) даже в отсутствие подключения к другим выходам. На практике такие соединения используются при необходимости считывания по единственной линии состояния множества выходов. В этом случае выходы находятся в состоянии высокого импеданса («Z-состояние») и не оказывают влияния на общую линию связи. Чтобы избежать появления неопределенного состояния, электрических помех и наводок (поскольку проводник оказывается неподключенным ни к одному источнику), используется «подтягивающий» (pull-up) резистор, обеспечивающий «слабую единицу». Если любой из выходов будет переведен в активное состояние, ему при необходимости не составит труда «перекрыть» этот уровень, обеспечив состояние логического нуля. Такая схема может быть описана и для ПЛИС:
q<= in0 whensel0 = ‘1’ else ‘Z’;
q <= in1 when sel1 = ‘1’ else ‘Z’;
q <= in2 when sel2 = ‘1’ else ‘Z’;
Несмотря на то, что в современных FPGA нет внутренних буферов с тремя состояниями, схема с таким поведением может быть реализована на базе мультиплексоров.
Для сигналов, описанных как std_logic, допустимо большинство операций – побитные логические, операции сравнения и сдвига. Формально, сигналы типа std_logic не могут участвовать в арифметических выражениях, однако на практике синтезаторы допускают это путем перегрузки (overloading) арифметических операторов.
4Тип std_logic является основным типом для представления сигналов в VHDL
Тип integer также довольно часто используется в VHDL. Как следует из названия, он описывает целые числа:
signal a : integer range 0 to 7;
Необязательный спецификатор range задает диапазон значений, которые может принимать описываемый сигнал. При синтезе для такого сигнала будет автоматически выделено количество разрядов, требуемое для представления всех значений из заданного диапазона. По умолчанию разрядность сигнала типа integer равна 32. Стандарт устанавливает разрядность целых чисел минимум 32, но не ограничивает максимальную разрядность, которую может реализовать производитель того или иного САПР. Для экономии ресурсов рекомендуется задавать действительно необходимые диапазоны значений.
Установка диапазона значений integer играет определенную роль в устранении логических ошибок проектирования, поскольку синтезатор имеет возможность отслеживать попытки присваивания сигналам значений, выходящих за пределы установленных диапазонов. Это не означает, что схемы, реализованные с применением VHDL, автоматически получают способность контролировать записываемые в них в процессе работы значения, речь идет о проверках в ходе синтеза схемы в САПР. Например, если объявлена переменная, хранящая один разряд десятичного числа, то для нее будет отведено 4 двоичных разряда, что, в принципе, позволит записывать туда числа от 0 до 15.
signaldecimal_digit : integer range 0 to 9;
При попытке выполнить присваивание decimal_digit<= 10 синтезатор сообщит об ошибке.
Тип real относится к несинтезируемым и представляет числа в формате с плавающей точкой.
signalx : real := 123.45;
Применение чисел такого типа ограничивается в основном вычислением задержек распространения сигналов, а также выполнения математических вычислений, на основании которых можно принять решение о генерации того или иного вида синтезируемого кода. Например, в качестве параметра в модуль на VHDL может быть передана емкость нагрузки или температура окружающей среды, которые удобно представить в формате с плавающей точкой. На основании анализа этих величин могут быть автоматически сгенерированы различные варианты синтезируемых узлов.
Тип character служит для представления отдельных символов. Онопределенследующимобразом.
type character is(nul, sol, stx, etx, eot, enq, ack, bel, . . . ‘@’, ‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’, . . .);
Данный тип является синтезируемым в том смысле, что задаваемые с помощью character значения могут быть представлены в двоичном виде и помещены в соответствующие регистры.
Физические типы используются для описания значений, имеющих единицы измерения. В VHDL предварительно задан только один тип – время.
Тип time предназначен для задания временных задержек.
constantTpd : time := 1 ns;
Допустимы следующие единицы измерения:
– fs – фемтосекунда;
– ps – пикосекунда;
– ns – наносекунда;
– us – микросекунда;
– ms – миллисекунда;
– s – секунда.
Объект типа time не является синтезируемым, т.е. невозможно создать синтезируемую конструкцию с требуемой задержкой, просто указав ее величину в операторе присваивания. Подобные типы переменных присутствуют в VHDL для облегчения создания возможно более адекватных моделей проектируемых устройств. Таким образом, разработчик, применяя тип time, самостоятельно отвечает за корректность приводимых им величин задержек.
Перечислимые (enumerated) типы определяются путем перечисления значений, которые может принимать переменная или сигнал такого типа.
type MY_TYPE is (RUN, STOP, PAUSE);
signal STATE : MY_STATE;
...
STATE<= RUN;
Использование перечислимых типов позволяет сделать исходный текст на VHDL более читаемым и близким к терминам решаемой задачи. Также рекомендуется описывать состояния конечных автоматов с использованием перечислимых типов, что позволяет программам синтеза выбирать оптимальный способ кодирования таких состояний. Более подробно это обсуждается в разделе, посвященному проектированию и использованию конечных автоматов.
Можно обратить внимание, что одинаковые значения, принадлежащие различным типам, не являются эквивалентными. Например, следующий код является некорректным.
typeTYPE_Ais (RUN, STOP, PAUSE);
type TYPE_B is (RUN, STOP, PAUSE);
signalsig_A : TYPE_A;
signalsig_B : TYPE_B;
…
sig_B<= sig_A; -- ошибка: сигналы разного типа
Несмотря на то, что оба типа имеют одинаковый набор идентификаторов, с точки зрения VHDL они являются разными (и к тому же могут иметь разный способ кодирования значений, выбранный синтезатором). Поэтому показанное присваивание не может быть выполнено.
Производные типы (subtype) служат для создания пользовательских типов на основе стандартных.
subtypetype_angle is integer range 0 to 359;
signalmy_angle : type_angle;
Два производных типа не являются совместимыми, даже если они основаны на одном базовом типе.
2.5.3 Композитные типы
Композитные типы – это типы, представляющие собой группы элементов более простых типов. Наиболее распространенным является тип std_logic_vector.
signala : std_logic_vector(3 downto 0);
В данном объявлении определяется сигнал a, представляющий собой группу элементов типа std_logic. Элементы имеют индексы 3 – 0. Можно использовать и порядок следования индексов по возрастанию:
signal b : std_logic_vector(0 to 3);
Удобнее использовать убывающий порядок следования индексов, что иллюстрируется следующим примером:
a<= “0001”;
b<= “0001”;
Присваивание значений отдельным разрядам производится в том же порядке, в котором они объявлены. Поэтому для сигнала a в единицу будет установлен разряд с индексом 0, а для сигнала b– с индексом 3. Таким образом, число, записываемое в b, соответствует десятичному значению 8 (0b1000), что выглядит несколько непривычно для восприятия. В этой связи и оказывается удобнее определять сигналы типа std_logic_vector с убыванием индексов.
4Сигналам std_logic_vector значения задаются в двойных кавычках (“0011”), тогда как для std_logic – в одинарных (‘0’). Это связано с принадлежностью этих сигналов к различным группам – std_logic является скалярным, а std_logic_vector – композитным типом. В то же время, значения для сигналов типа integer записываются без кавычек.
Следующим композитным типом являются массивы (arrays). Это группы элементов одинакового типа, например:
typeTmem is array (0 to 255) of std_logic_vector(7 downto 0);
signalMem : Tmem;
В примере определяется тип Tmem, который представляется как массив элементов std_logic_vector(7 downto 0). В массиве 256 элементов с индексами 0 – 255. Далее определяется сигнал Mem типа Tmem. Можно обращаться к отдельным элементам такого массива:
signala : std_logic_vector(7 downto 0);
…
a<= Mem(0);
Из этого примера видно, что каждый элемент массива Mem является 8-разрядным вектором.
Можно рассматривать сигналы типа std_logic_vector как одномерный массив элементов std_logic. Соответственно, возможен и доступ к отдельным разрядам или группам разрядов таких сигналов. Ниже показаны примеры оперирования с сигналами различной разрядности, выделения и объединения элементов.
signal a : std_logic_vector(7 downto 0);
signal b, c : std_logic_vector(3 downto 0);
signal d : std_logic;
a(3 downto 0) <= b;
a <= b & c;
d <= a(5);
a <= (7|6 => ‘1’, 3 downto 1 => ‘0’);
a<= (others => ‘0’);
Ранее не описанным примером использования является оператор конкатенации &. Он «склеивает» сигналы, находящиеся по левую и правую сторону от него, образуя новый сигнал композитного типа.
Нельзя выполнять присваивание сигналов типа std_logic_vector с отличающимся порядком изменения индексов.
signal a : std_logic_vector(3 downto 0);
signal b : std_logic_vector(0 to 3);
a<= b; -- порядок изменения индексов различен
Записи (record) представляют собой группы сигналов различного типа.
typeTCoordis record
X, Y : integer range 0 to 1023;
Visible : std_logic;
end record ;
signal Point1, Point2 : TCoord;
Point1.X <= 123;
Point2.Visible <= ‘1’;
Point2 <= Point1;
Общим правилом записи выражений на VHDL является строгое соответствие типов и разрядностей присваиваемых значений. Далее будут рассмотрены функции преобразования типов, которые включают в себя и удобные формы записи функций преобразования разрядности.
Строки (string) – это массивы символов (character). Они используются в основном при создании тестов:
constantstr: string (1 to 5) := "Error";
Задав набор текстовых сообщений, можно в дальнейшем использовать ссылки на них.
2.5.4 Преобразования типов
Данные, представленные типом std_logic_vector, не могут быть автоматически интерпретированы как десятичное число. Отдельные элементы такого вектора не рассматриваются средствами синтеза как разряды двоичного числа, таким образом, “0011” не вполне эквивалентно числу 3 с точки зрения VHDL. Например, нельзя выполнить сравнение “0011” < “1000”, поскольку группы разрядов не трактуются синтезатором как 4-разрядные двоичные числа, а только как отдельные двоичные сигналы, сгруппированные в один композитный объект. Для выполнения операций сравнения следует преобразовать один тип в другой. Для этого можно использовать следующие функции.
Функция conv_integer преобразует сигнал или значение типа std_logic_vector в целочисленный тип.
signalx_slv : std_logic_vector (7 downto 0);
signalx_int : integer range 0 to 255;
x_int<= conv_integer(x_slv);
Внимание! Функция conv_integer по умолчанию не подключена в САПР ISE или Vivado. Для ее использования в области объявления библиотек следует добавить строки:
Use ieee.std_logic_arith.all;
Use ieee.std_logic_unsigned.all;
В новых версиях САПР рекомендуется использовать пакет numeric_std, в котором объявлена функция to_integer. Однако, ввиду того, что в numeric_std допускается работа как с числами без знака, так и с числами со знаком, требуется явно указать, какой из этих форматов необходимо использовать. Поэтому преобразование в integer будет выглядеть следующим образом:
x_int<= to_integer(unsigned(x_slv));
Обратное преобразование выполняет функция conv_std_logic_vector.
x_slv<= conv_std_logic_vector(x_int, 8);
Второй параметр, равный в примере 8, задает количество разрядов, в которые следует поместить результат преобразования. Это связано с тем, что std_logic_vector не рассматривается синтезатором как единый объект, отдельные разряды которого представляют соответствующие разряды двоичного числа со своими весами. Если преобразование std_logic_vector в integer позволяло говорить о том, что группа двоичных разрядов должна быть скомпонована так, чтобы получить значение скалярного сигнала, то обратное утверждение, строго говоря, неверно. Кроме того, преобразование констант в std_logic_vector содержит в себе некоторую неопределенность, поскольку для хранения числа 1 достаточно, вообще говоря, одного разряда. Именно поэтому количество двоичных разрядов, которое должен занять результат преобразования, указывается явно.
Чтобы присваивание было корректным, должны совпадать не только типы, но и разрядности выражений в левой и правой частях присваивания. Поскольку сигналы типа std_logic_vector в проектах могут иметь различную разрядность, возникает проблема ее согласования. Проще всего присвоить фрагмент сигнала большей разрядности сигналу меньшей разрядности.
signal a16 : std_logic_vector(15 downto 0);
signal a8 : std_logic_vector(7 downto 0);
a8 <= a16(7 downto 0);
Чтобы выполнить обратное преобразование, необходимо задать значения для старших 8 разрядов сигнала a16 (впрочем, можно записывать a8 и в старшие разряды, и вообще в любые фрагменты a16, размером 8 разрядов).
a16(7 downto 0) <= a8;
a16(15 downto 8) <= a8;
Можно расширить имеющиеся 8 разрядов в правой части присваивания с помощью оператора конкатенации &.
a16 <= “00000000” &a8;
Наконец, можно воспользоваться функциями ext («расширить») и sxt (signextension, «расширить со знаком»).
a16 <= ext(a8, 16);
a16 <= sxt(a8, 16);
Как и для функции conv_std_logic_vector, второй параметр представляет собой разрядность получаемого результата. Различие между функциями заключается в том, что ext дополняет старшие разряды нулями, а sxt – значением старшего разряда первого операнда, обеспечивая тем самым его знаковое расширение.
Можно напомнить смысл знакового расширения. В дополнительной двоичной арифметике используется тот факт, что выражение (0 – 1) дает в результате двоичное представление, в котором все разряды установлены в 1. Прибавление к нему 1 вызовет переполнение и появление бита переноса (0b11111111 + 1 = 0b100000000), но если проигнорировать появляющийся старший разряд, то оставшееся число равно 0. Таким образом, можно считать, что полученное двоичное представление с единицами во всех разрядах соответствует десятичному значению -1 («минус один»). Аналогично, 0b11111110 можно трактовать как -2, поскольку прибавление к этому числу 2 вызовет обнуление имеющихся разрядов с появлением бита переноса в старшем разряде, который будет проигнорирован. При таком подходе можно и число 0b00000001 трактовать как -255, т.к. прибавление 255 даст в результате нули в имеющихся двоичных разрядах. Однако на практике принимают, что число с единицей в старшем двоичном разряде является отрицательным, а с нулем – положительным. Тогда 8-разрядное двоичное число может принимать значения -128..+127 в дополнительной двоичной арифметике.
При таком подходе знаковое расширение требуется для того, чтобы -1 (0b11111111) после его расширения не превращалось в 255 из-за появления новых разрядов, которые будут равны нулю при использовании ext. Если скопировать старший разряд во вновь добавляемые, как это делает sxt, то любое число, которое трактовалось как отрицательное, останется отрицательным и после преобразования.
2.5.5 Указатели
Указатели, называемые в VHDLaccesstypes, могут использоваться при создании тестов. Пример использования указателя показан ниже:
typePintegerisaccessinteger;
variableptr : Pinteger;
ptr := new integer;
ptr.all := 5;
2.5.6 Файлы (указатели на файлы).
Файловые переменные, как и указатели, часто используются при создании тестов и генерации отчетов. Такие переменные в сочетании с рядом библиотечных функций обеспечивают доступ к файлам, позволяя выполнять стандартные для языков программирования операции чтения и записи. Данные в файлах при этом используются как тестовые последовательности, а сохраняются в файлы результаты выполнения и диагностические сообщения.