Оглавление
Введение

Классы и объекты

Комментарии

Типы данных

Подробно о std_logic

О массивах в языке VHDL

Операции в языке VHDLv Операторы VHDL

Интерфейс

Архитектура

Процессы

Атрибуты

Подпрограммы VHDL

Библиотеки VHDL

Тестирование

Заключение

Приложение А

Приложение B. Примеры на VHDL

ВВЕДЕНИЕ
…товарищи, а в каком ключе докладывать.

Язык VHDL был разработан в 1983 г. по заказу МО США для формального описания логических схем на всех этапах разработки электронных систем. Аббревиатура VHDL (Very High Speed Integrated Circuit Hardware Description Language) переводится как язык описания высокоскоростных интегрированных схем.

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

Международный стандарт — IEEE Std 1076 — 2004 IEEE Standard VHDL Language Reference Manual.

Отечественный стандарт — ГОСТ Р 50754−95 Язык описания аппаратуры цифровых систем VНDL. Описание языка.

И т. д. и т. п. Ничего интересного? Тогда ответьте на вопрос и проверьте себя — можно ли VHDL-выражение 1 заменить выражением 2 и наоборот:

  1. y <= x;
  2. if x = '0' then y <= '0' ; else y <= '1' ; end if ;

Ответ: Честно говоря, для правильного ответа необходимо знать, какой тип данных у объектов х и у. Дело в том, что если мы имеем дело с типом BIT, то выражения вполне идентичны. А теперь посмотрим, что будет, если объекты х и у имеют тип std_logic? Если Вы уже догадались в чем дело, то Вы вполне владеете основами VHDL (или выше того) и можете пропустить дальнейшие рассуждения.

Итак, в VHDL есть несколько типов данных, два из них мы упомянули выше: bit и std_logic. Их множества значений таково:

  • bit: {'0', '1'};
  • std_logic: { 'U','X','0','1','Z','W','L','H','- ', }

Как видите, объекты типа std_logic могут принимать девять различных значений, а объекты типа bit только два. Теперь рассмотрим такой код:

    • &nbsp&nbsp&nbsp signal x, y: std_logic;

  • &nbsp&nbsp&nbsp if x = 'U' then y <= 'U' ;
  • elsif x = 'X' then y <= 'X' ;
  • elsif x = '0' then y <= '0' ;
  • elsif x = '1' then y <= '1' ;
  • elsif x = 'Z' then y <= 'Z' ;
  • elsif x = 'W' then y <= 'W' ;
  • elsif x = 'L' then y <= 'L' ;
  • elsif x = 'H' then y <= 'H' ;
  • elsif x = '- ' then y <= '- ' ;
  • end if ;

Вышеприведенный VHDL код теперь идентичен выражению 1 для типа std_logic. Надеюсь теперь понятен смысл задачки, решенной нами. Ну вообще-то они не совсем идентичны, если мы будем синтезировать эти выражения. Тут все будет зависеть от способов оптимизации и прочего, но об этом позже. А сейчас перейдем к более подробному рассмотрению языка VHDL.

КЛАССЫ И ОБЪЕКТЫ
… а вы товарищ из буржуев будете? Или пролетарий?

В отличие от приплюснутого Си в VHDL не предусмотрена возможность создавать новые классы, они уже встроены и их всего три:

  1. constant — константа.
  2. variable — переменная;
  3. signal — сигнал;

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

Прежде чем использовать какой либо объект, его нужно объявить членом какого-нибудь класса. Например, объявляются три объекта х, у и gnd:

  • constant gnd: bit :='0';
  • variable y: bit;
  • signal z: bit;

Далее мы можем записать следующий VHDL код:

  • y := gnd;
  • z <= y;

Как видите, оператор присваивания для сигналов отличается от аналогичных для объектов других классов. Этот код дан для примера т. к. мы спокойно можем написать z <= gnd; или еще проще — z <= '0'; Без первых двух классов можно обойтись в любом синтезируемом проекте, а без класса signal — вряд ли.

Давайте разберемся. Первое — после объявления константы должно быть её определение (constant const: bit :='1';) Константы могут объявляться в интерфейсах, архитектурах, процессах, процедурах и функциях. Если символов «:=" нет в декларации константы, то константа называется задержанной. Такие константы могут быть в разделе деклараций пакета. Соответствующая полная декларация должна быть в теле пакета. Второе — сигналы объявляются в области объявлений архитектурного тела и видны во всем этом теле или в области объявлений интерфейса, тогда они видны во всех архитектурах, использующих данный интерфейс. Порты по умолчанию являются сигналами. Третье — переменные объявляются в области деклараций процесса, функции или процедуры и видны только там. Правда есть возможность сделать переменную глобальной, — это объявить ее в архитектуре или в интерфейсе следующим VHDL-кодом:

  • shared variable y: bit;

Да и еще, переменным можно задавать начальное значение при объявлении:

  • variable y: bit:='1';

КОММЕНТАРИИ
… прокомментируйте вашу позицию, а то непонятно…

Комментарии отделяются от основного VHDL-кода двойным штрихом и действуют до конца строки:

  • -------------------------------
  • -- Пример комментариев --
  • -------------------------------
  • --
  • -- Управление индикаторами сигналов LINK
  • nLEDL1 <= GND when nLINKA ='0' else TRI; -- канал А
  • nLEDL2 <= GND when nLINKB ='0' else TRI; -- канал В
  • --

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

  • -------------------------------
  • -- Пример бесполезных
  • -- комментариев
  • -------------------------------
  • --
  • x <= '0'; -- Приравниваем сигнал х нулю.
  • --

Вот в принципе и все, что можно сказать о комментариях.

ТИПЫ ДАННЫХ
… гуляют тут разные типы, прохода от них нет.

Про типы обычно долго и нудно ведут пояснения и рассуждения. Мы этого делать не будем, просто покажем их объявления в библиотеке std с нашими комментариями:

  • type boolean is (false, true); -- логический тип
  • type bit is ('0', '1'); -- битовый тип
  • type character is ( -- символьный тип

    nul, soh, stx, etx, eot, enq, ack, bel,
    bs, ht, lf, vt, ff, cr, so, si,
    dle, dc1, dc2, dc3, dc4, nak, syn, etb,
    can, em, sub, esc, fsp, gsp, rsp, usp,

    ' ', '!', '"', '#', '$', '%', '&', ''',
    '(', ')', '*', '+', ',', '-', '.', '/',
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', ':', ';', '<', '=', '>', '?',

    '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
    'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
    'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
    'X', 'Y', 'Z', '[', '\', ']', '^', '_',

    '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
    'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
    'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
    'x', 'y', 'z', '{', '|', '}', '~', del,

    c128, c129, c130, c131, c132, c133, c134, c135,
    c136, c137, c138, c139, c140, c141, c142, c143,
    c144, c145, c146, c147, c148, c149, c150, c151,
    c152, c153, c154, c155, c156, c157, c158, c159,

    -- the character code for 160 is there (NBSP),
    -- but prints as no char


    ' ', 'Ў', 'ў', 'Ј', '¤', 'Ґ', '¦', '§',
    'Ё', '(c)', 'Є', '"', '¬', '­', '®', 'Ї',
    '°', '±', 'І', 'і', 'ґ', 'µ', '¶', '·',
    'ё', '№', 'є', '"', 'ј', 'Ѕ', 'ѕ', 'ї',

    'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ж', 'З',
    'И', 'Й', 'К', 'Л', 'М', 'Н', 'О', 'П',
    'Р', 'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч',
    'Ш', 'Щ', 'Ъ', 'Ы', 'Ь', 'Э', 'Ю', 'Я',

    'а', 'б', 'в', 'г', 'д', 'е', 'ж', 'з',
    'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п',
    'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч',
    'ш', 'щ', 'ъ', 'ы', 'ь', 'э', 'ю', 'я');

  • type severity_level is (note, warning, error, failure); -- перечислимый тип
  • type integer is range -2147483647 to 2147483647; -- целый тип
  • type real is range -1.0E308 to 1.0E308; -- вещественный тип
  • type time is range -2147483648 to 2147483647; -- целый тип

По аналогии пользователь может создавать свои типы данных, например, определим перечислимый тип — день недели:

  • type day_t is (mon, tue, wen, thu, fri, sat, sun);
  • signal day : day_t;
  • day <= mon;

Сразу нужно отметить, что приведенный выше перечислимый тип вполне синтезируемый, но на кристалле он будет уже работать в виде ноликов и единичек. Синтезатор просто переведет данное множество значений в цифровое. Какое будет множество? А все зависит от настроек, например такое: {„000“, „001“, …,„110“}, а может такое: {„0000001“, „0000010“, …,„1000000“}

Типы могут иметь подтипы (subtype). Так в библиотеке STD определены следующие подтипы:

  • subtype natural is integer range 0 to integer’high;-- подтип натуральных чисел
  • subtype pozitive is integer range 1 to integer’high;-- подтип положительных чисел

Здесь integer’high — наибольшее значение типа integer. В данном случае integer’high = 2147483647 и наоборот integer’low = -2147483647. Но это больше касается атрибутов, о которых мы поговорим позже.

Конечно, не обойтись без массивов и векторов:

  • type string is array (positive range <>) of character;
  • type bit_vector is array (natural range <>) of bit;
  • signal adddr: bit_vector (7 downto 0); -- определили 8-ми битовый вектор
  • constant txt: string := „VHDL“; -- определили строковую константу

Все библиотечные типы std переопределять нет необходимости, они по умолчанию подключаются к проекту. Чтобы уж окончательно разобраться с библиотекой STD, давайте просмотрим ее до конца:

  • units -- единицы времени
    fs;
    ps = 1000 fs;
    ns = 1000 ps;
    us = 1000 ns;
    ms = 1000 us;
    sec = 1000 ms;
    min = 60 sec;
    hr = 60 min;
    end units;
  • subtype delay_length is time range 0 fs to time’high;
  • impure function now return delay_length;
  • type file_open_kind is ( read_mode, write_mode, append_mode); -- вид доступа к открываемому файлу
  • type file_open_status is ( open_ok, status_error, name_error, mode_error); -- результат открытия файла
  • attribute foreign: string; -- атрибуты рассмотрим позже
  • attribute syn_enum_encoding: string;
  • attribute syn_enum_encoding of character: type is „sequential“;

Ну кажется все. Теперь давайте попробуем все это систематизировать:

Types

ПОДРОБНО О STD_LOGIC
… а вот кому набор стандартных ложек.

Давайте посмотрим, хватает ли нам типов для описания логических схем или их моделирования. Ну возьмем хотябы состояние высокого импеданса. Ни один из вышеперечисленных типов такой возможности не дает. Поэтому были введены типы std_ulogic и std_ulogic_vector:
type std_ulogic is (
'U', -- Неинициализированный. Этот сигнал не был задан еще.
'X', -- Сильный неизвестный сигнал.
'0', -- Сильный 0
'1', -- Сильная 1
'Z', -- Высокий импеданс
'W', -- Слабый неизвестный сигнал
'L', -- Слабый 0
'H', -- Слабая 1
'-' -- Не имеет значения какой сигнал
);

type std_ulogic_vector is array (natural range <> ) of std_ulogic;

Такие объявления типов находятся в библиотеке std_logic_1164. В большинстве книгах по VHDL на этом все и заканчивается, мы же рассмотрим эти типы поподробнее.

Итак, начнем с конца. Тип std_ulogic_vector есть просто вектор ранее объявленного типа std_ulogic. Это аналогично типу bit_vector. И мы можем спокойно написать следующие выражения:

  • signal a: std_ulogic_vector (0 to 7);
  • signal b: std_ulogic_vector (7 downto 0);

А теперь перейдем к типу std_ulogic. Как видим это перечислимый тип, состоящий из 9 символов. Первый символ 'U' обозначает неинициализированные данные. Ну представьте себе ОЗУ, после включения питания. Реально конечно там будут либо нолики, либо единички, поэтому данный символ не годится для проектирования синтезируемой логики, но активно используется для моделирования.

Символы 'X', '1' и '0' обозначают сильные сигналы. Что означает сильные сигналы? Давайте посмотрим на рисунки 2 и 3. Рисунок 2 не требует комментариев, а вот рисунок 3 поясним. Конечно, реально в схеме будет или '1' или '0', просто мы точно не знаем значение. И конечно для синтеза логических схем символ 'X' не подходит, но, как и 'U' часто используется при моделировании.

Рисунки 2, 3



На рисунке 4 показаны пояснения к символу 'Z'. В принципе это простой высокий импеданс. Этот символ подходит как для моделирования, так и для синтеза, но следует отметить, что в ПЛИС такой сигнал можно реализовать только на выходных каскадах микросхемы.

Рисунок 4



Теперь давайте посмотрим на слабые сигналы (рисунки 5, 6, 7). Думаю понятно, почему они слабые, просто их можно „поддавить“. При моделировании используются в основном как нагрузка на открытом коллекторе (или его имитации).

Рисунки 5, 6, 7

Вот пример на VHDL открытого коллектора с нагрузкой (на рисунке 8 приведена схемная реализация данного кода):

  • out <= '0' when in = '1' else 'H';

Причем некоторые ПЛИС содержат такие резисторы в своих выходных каскадах, а некоторые синтезаторы даже подключат их, встретив такой VHDL-код. В крайнем случае, это можно сделать вручную через настройки выходов, правда, сопротивление таких резисторов порядка 50 кОм.

Рисунок 8



И наконец, загадочный символ '-', который используется только при моделировании и означает, что Вам плевать какое значение имеет сигнал. Автор никогда его не использовал, но может, есть такие ситуации, когда его использование целесообразно. Пример такого использования показан на рисунке 9.

Рисунок 8

А теперь давайте разберемся, причем здесь std_ulogic, когда мы хотели подробно говорить об std_logic. А все очень просто — буква 'u' в std_ulogic означает unresolved, что означает неразрешенный. Следовательно, std_logic является разрешенным подтипом, что, кстати, следует из его объявления в библиотеке std_logic_1164:

  • subtype std_logic is resolved std_ulogic;

И далее объявляется вектор:

  • type std_logic_vector is array (natural range <> ) of std_logic;

Как видите, подтип std_logic образуется из типа std_ulogic c помощью разрешающей функции resolved, которая также объявлена и определена в библиотеке std_logic_1164. Для разрешения данных типа std_logic, функция resolved использует разрешающую таблицу, VHDL-код которой приведен ниже:

type stdlogic_table is array(std_ulogic, std_ulogic) of std_ulogic;
constant resolution_table: stdlogic_table := (
-- ---------------------------------------------------------
-- | U X 0 1 Z W L H — | |
-- ---------------------------------------------------------
('U', 'U', 'U', 'U', 'U', 'U', 'U', 'U', 'U'), -- | U |
('U', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'), -- | X |
('U', 'X', '0', 'X', '0', '0', '0', '0', 'X'), -- | 0 |
('U', 'X', 'X', '1', '1', '1', '1', '1', 'X'), -- | 1 |
('U', 'X', '0', '1', 'Z', 'W', 'L', 'H', 'X'), -- | Z |
('U', 'X', '0', '1', 'W', 'W', 'W', 'W', 'X'), -- | W |
('U', 'X', '0', '1', 'L', 'W', 'L', 'W', 'X'), -- | L |
('U', 'X', '0', '1', 'H', 'W', 'W', 'H', 'X'), -- | H |
('U', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X') -- | - |
);

Данная таблица определяет, какой сигнал получится в итоге „столкновения“ двух сигналов типа std_logic. Например, приходят сигналы '0' и 'H', смотрим, что в месте пересечения соответствующих строки и столбца получается '0'. И это не удивительно, если вспомнить, что сигнал '0' — сильный (рисунок 2), а 'H' — слабый (рисунок 5). Аналогично можно разрешить ситуацию и с другими сигналами.

Кроме того, в библиотеке std_logic_1164 переопределены операторы and, or, nand, nor, xor, xnor и not для типов std_ulogic и std_logic. И в той же библиотеке определены несколько полезных функций.

А теперь о главном. В отличие от библиотеки STD, библиотеку std_logic_1164 нужно подключать с помощью следующих VHDL инструкций:

  • library ieee;
  • use ieee. std_logic_1164.all;
  • -- Дальше можно делать объявления std_logic
  • signal a, b: std_logic;
  • signal c: std_logic_vector (7 downto 0);
  • shared variable d: std_logic_vector (7 downto 0):="01010101»;

О МАССИВАХ В ЯЗЫКЕ VHDL
… Районы, кварталы, жилые массивы …

В разделах «ТИПЫ ДАННЫХ» и «ПОДРОБНО О STD_LOGIC» мы уже встречались с массивами. Как известно, массив — именованный набор однотипных переменных, доступ к которым осуществляется по индексу. Массивы могут быть одномерными и многомерными, что определяется количеством индексов. При использовании массивов в VHDL, разработчику следует учитывать, что синтезаторы обычно не поддерживают многомерные массивы, за исключением массивов векторов. Массив не может состоять из файлов.

В зависимости от наличия или отсутствия размерности, объявление типа массива может быть двух видов:

  1. type type_name is array (range) of element_type;
  2. type type_name is array (type range <>) of element_type;

Первый вид используется для типов с заданной размерностью и при объявлении объекта размерность уже будет задана типом:

  • type BYTE is array (7 downto 0) of BIT; -- объявили 8-ми битовый тип
  • signal BYT1: BYTE; -- объявили 8-ми битовый массив

Второй вид используется для типов без размерности, тогда размерность должна указываться при объявлении объекта:

  • type MATRIX is array (POSITIVE range <>) of REAL; -- объявили вектор реальных чисел
  • signal MATRIX1: MATRIX (1 to 3); -- объявили вектор из трех реальных чисел

Можно ли обойтись без массивов в VHDL? Можно. Но чем крупнее проект, тем проще Вам будет с массивами, нежели без них. Вот несколько примеров применения массивов — всевозможные шины, регистры, счетчики, сумматоры, ОЗУ, ПЗУ и т. д. и т. п. И чем больше разрядность этих устройств, тем сложнее обойтись без массивов.

ОПЕРАЦИИ В ЯЗЫКЕ VHDL
… операция: действие, воздействие, деятельность, сделка, поход, кампания, дело, дельце…

Мы не будем рассматривать подробно каждую операцию языка VHDL, пологая, что здесь люди образованные. Просто перечислим, какие операции в VHDL можно использовать:

  • логические операции (and, or, nand, nor, xor, xnor);
  • операции сравнения (=, /=, <, <=, >, >=);
  • операции сдвига (sll, srl, sla, sra, rol, ror);
  • операции сложения (+, -, &);
  • операции смены знака числа (+, -);
  • умножение деление (*, /, mod, rem);
  • прочие (**, abs, not)

Отметим, что операции перечислены в порядке увеличения приоритета выполнения (т. е. от низшего к высшему). Ну и как обычно, при равенстве приоритетов, операции выполняются слева направо и скобки дают высший приоритет. Ну и чтоб не было скучно, можем продемонстрировать на простом примере один подводный камень, с которымсталкиваются практически все начинающие разработчики на VHDL. Пусть у нас будет такой VHDL-код:

    • signal x, y, z: std_logic;
    • x <= '1' when ( y = '1' and z = '0') else '0';
    • теперь пусть сигнал х должен управлять чем-либо по
if
       —
else
    , а чтобы сэкономить время на вводе с клавиатуры, копируем выделенное красным и вставляем в следующий VHDL-код:
  • if (x <= '1') then
  • else
  • end if;

Вам может показаться это смешным, а бывали случаи, что молодые специалисты по нескольку дней разбирались, почему это у них никогда не выполняется ветка else и потом приходили и говорили, что у них глючит САПР. А с точки зрения психологии, это вполне нормально. Вспомните нашу математику, и Вы поймете, что у нас принято две разные операции — сравнения и присваивания обозначать одним словом «равняется» и одним знаком «=". Кто забыл, напомним такие выражения — „если х равен y“ и „х равен y“. В английском эти операции разделены на equality и set.

ОПЕРАТОРЫ VHDL
— а кто такие опсосы?
— это операторы сотовой связи.

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

Последовательные операторы в VHDL (не надо путать с последовательной логикой) определяют алгоритмы выполнения подпрограмм и процессов. Они выполняются в том порядке, в каком расположены в VHDL-коде, отсюда и название такое. Одно важное замечание: все последовательные операторы могут использоваться только в теле процесса, функции или процедуры. К последовательным операторам языка VHDL относятся следующие операторы:

  • assert (последовательный);
  • case;
  • exit;
  • if;
  • loop;
  • next;
  • null;
  • report;
  • return;
  • wait;
  • последовательные операторы присваивания.

Примеры использования последовательных операторов:

  • -- Использование операторов loop, next, exit, if, присваивание
  • -- Несинтезируемый фрагмент VHDL-кода подсчитывает кол-во несовпадающих бит в двух массивах
  • neq := 0;
  • L1: for i in 0 to 31 loop
  • next L1 when mem1 (i) = mem2 (i);
  • for j in 7 downto 0 loop
  • crash := 1;
  • exit L1 when (mem1 (i)(j) = 'X' or mem2 (i)(j) = 'X');
  • crash := 0;
  • if (mem1 (i)(j) /= mem2 (i)(j)) then neq := neq+1;
  • end loop;
  • end loop L1;
  • -- Использование операторов wait, case, if
  • -- Синтезируемый фрагмент VHDL-кода управляет состояниями сфетофора
  • process begin
  • wait until clk;
  • if (trig = '1') then
  • case state is
  • when red => state <= green;
  • when green => state <= yellow;
  • when yellow => state <= red;
  • end case;
  • end if;
  • end process;

Параллельные операторы в VHDL используются для определения блоков и процессов, которые описывают общее поведение или структуру проекта. Параллельные операторы VHDL, как следует из названия, выполняются (или не выполняются) параллельно друг другу и независимо от расположения в VHDL-коде. К таким операторам относятся:

  • assert (параллельный);
  • block;
  • generate;
  • process;
  • применение компонентов (port map …);
  • параллельные операторы присваивания.

Примеры использования оператора block:

  • -- Пример защелки с использованием охранного выражения
  • L1: block (LE = '1')
  • begin
  • Q <= guarded D; -- Выполнится только при LE = '1'
  • end block L1;
  • -- Пример простых блоков
  • BLOCK1: block
  • signal Q: std_logic;-- Этот сигнал Q виден только в BLOCK1
  • begin
  • Q <= A and B;
  • end block BLOCK1;
  • -----------------------------------
  • BLOCK2: block
  • signal Q: std_logic;-- Этот сигнал Q виден только в BLOCK2
  • begin
  • Q <= C and D;
  • end block BLOCK2;
  • -- Пример вложенных блоков
  • BLOCK1: block
  • signal Q: std_logic;-- Этот сигнал Q виден в BLOCK1 и в BLOCK2
  • begin
  • Q <= A and B;
  • -----------------------------------
  • BLOCK2: block
  • signal Q1: std_logic;-- Этот сигнал Q1 виден только в BLOCK2
  • begin
  • Q1 <= Q and C;
  • end block BLOCK2;
  • -----------------------------------
  • end block BLOCK1;

Следует отметить, что охранные блоки не синтезируются ни одним из известных автору синтезатором. Другие виды блоков нормально синтезируются, но и без них можно вполне обходиться. Во всяком случае, автор, кроме как в примерах, больше нигде не встречал применение блоков. Хотя на вкус и цвет…

Остальные параллельные операторы языка VHDL встретятся в последующих разделах, и мы не будем рассматривать их в этом разделе.

ИНТЕРФЕЙС
… да вот фейс-то у меня не подходящий.

Ну чтож, вот мы и подошли к какому-то началу. Как говорится интерфейс он и в Африке интерфейс. То есть это те же входные и выходные сигналы, а также какие-нибудь передаваемые в архитектуру параметры, оформленные по правилам языка VHDL. В общем виде объявление интерфейса выглядит следующим образом:

  • entity NAME_ENTITY is
  • ----------------------------
  • generic (
  • -- Здесь указываются параметры);
  • port (
  • -- Здесь указываются порты);
  • -- Область объявлений
  • begin --ставится, если далее следуют параллельные операторы интерфейса
  • -- Параллельные операторы (обычно assert), вызовы подпрограмм
  • end NAME_ENTITY;

Приведем пример общего случая VHDL интерфейса, при этом не забываем объявлять библиотеки, если необходимо:

  • library ieee;
  • use ieee. std_logic_1164.all;
  • entity ADDER_N is
  • ----------------------------
  • generic (
  • -- Задаем разрядность сумматора
  • N: natural := 4);
  • ----------------------------
  • port (
  • a, b: in std_logic_vector (0 to N-1); -- Операнды
  • s: out std_logic_vector (0 to N-1); -- Сумма
  • c: out std_logic -- Перенос
  • );
  • ----------------------------
  • constant GND: std_logic := '0';
  • constant VCC: std_logic := '1';
  • constant TRI: std_logic := 'Z';
  • ----------------------------
  • begin
  • ----------------------------
  • assert N &gt 1
  • report „N is too small“
  • severity error;
  • ----------------------------
  • end ADDER_N;

Этот VHDL интерфейс проиллюстрирован на рисунке 10.

Рисунок 10

А теперь то же самое, но без generic:

  • library ieee;
  • use ieee. std_logic_1164.all;
  • entity ADDR4 is
  • port (
  • a, b: in std_logic_vector (0 to 3); -- Операнды
  • s: out std_logic_vector (0 to 3); -- Сумма
  • c: out std_logic -- Перенос
  • );
  • end ADDR4;

Этот VHDL интерфейс проиллюстрирован на рисунке 11.

Рисунок 11
А может быть и пустой интерфейс. Это когда у Вас крутится все внутри и нет никаких связей с внешним миром. Используется для создания тестовых VHDL моделей:
  • library ieee;
  • use ieee. std_logic_1164.all;
  • entity TEST_BENCH is
  • end TEST_BENCH;
Интерфейс может быть как внешним для проекта, так и внутренним, применяемым для компонентов проекта. Про компоненты мы поговорим позже. Порты VHDL-интерфейса могут быть следующих видов:
  • in — входной порт (только для чтения значения сигнала);
  • out — выходной порт (только для записи значения сигнала);
  • inout — двунаправленный порт (чтение и запись);
  • buffer — выходной порт, значение которого можно считывать;
  • linkage — двунаправленный порт с ограниченным использованием (практически не используется).
Интерфейс может быть связан с несколькими архитектурами, о чем мы и поговорим далее.

АРХИТЕКТУРА
… после программы ВРЕМЯ — архитектор перестройки.

Архитектура содержит две основные части:

  1. часть, содержащую описания (декларации);
  2. часть, содержащую исполняемые операторы.

В общем виде архитектура выглядит следующим образом:

  • architecture NAME_ARCHITECTURE of NAME_ENTITY is
  • -- Здесь содержатся:
  • -- - описания типов данных;
  • -- - функции и процедуры;
  • -- - компоненты более низкого уровня иерархии;
  • -- - описания сигналов и глобальных переменных.
  • begin
  • -- Здесь содержатся исполняемые операторы.
  • end NAME_ARCHITECTURE;

Теперь напишем простой VHDL код для компонента NAND c двумя входами:

  • -------------------------------------------
  • -- VHDL код для компонента NAND2
  • -------------------------------------------
  • library ieee;
  • use ieee. std_logic_1164.all;
  • ---------------------------------
  • entity NAND2 is
  • port (
  • a, b: in std_logic;
  • c: out std_logic
  • );
  • end NAND2;
  • ---------------------------------
  • architecture aaa of NAND2 is
  • begin
  • c <= not (a and b);
  • end aaa;

Ну вот, у нас получился вполне законченный синтезируемый VHDL-код. Поверьте, он будет воспринят любым существующим синтезатором. Конечно, это очень простой пример, но не все сразу. Теперь мы покажем, как используются компоненты и сигналы, несколько усложнив схему (рисунок 12):

Рисунок 12
  • --------------------------------------------------
  • -- VHDL код для трех компонентов NAND2
  • --------------------------------------------------
  • library ieee;
  • use ieee. std_logic_1164.all;
  • ---------------------------------
  • entity NAND2X3 is
  • port (
  • a, b, c, d: in std_logic;
  • q: out std_logic
  • );
  • end NAND2X3;
  • ---------------------------------
  • architecture bbb of NAND2X3 is
  • signal s1, s2: std_logic;
  • component NAND2
  • port (
  • a, b: in std_logic;
  • c: out std_logic);
  • end component;
  • begin
  • D1: NAND2 port map (a => a, b => b, c => s1);
  • D2: NAND2 port map (a => c, b => d, c => s2);
  • D3: NAND2 port map (a =>s1, b =>s2, c =>q);
  • end bbb;

Данный VHDL код использует компонент NAND2, описанный ранее. Вообще-то данный пример приведен в учебных целях и автор никогда бы так не стал писать, хотя любой синтезатор поймет и правильно воспримет этот код. Почему так писать не стоит. Да хотя бы, потому что можно написать проще и главное — понятнее. Метод, которым мы описали схему, изображенную на рисунке 12, называется структурным описанием. Такой метод, по личному мнению автора, имеет право на жизнь в крупных проектах, т. е. когда мы разбиваем проект на несколько компонентов. А теперь давайте воспользуемся другим методом описания — поведенческим:

  • --------------------------------------------------
  • -- VHDL код для трех компонентов NAND2
  • --------------------------------------------------
  • library ieee;
  • use ieee. std_logic_1164.all;
  • ---------------------------------
  • entity NAND2X3 is
  • port (
  • a, b, c, d: in std_logic;
  • q: out std_logic
  • );
  • end NAND2X3;
  • ---------------------------------
  • architecture ccc of NAND2X3 is
  • signal s1, s2: std_logic;
  • begin
  • s1 <= not (a and b);
  • s2 <= not (c and d);
  • q <= not (s1 and s2);
  • end ccc;

А если учесть, что !(!(a*b)*!(c*d)) = (a*b)+(c*d), то можно переписать VHDL-код следующим образом:

  • --------------------------------------------------
  • -- VHDL код для трех компонентов NAND2
  • --------------------------------------------------
  • library ieee;
  • use ieee. std_logic_1164.all;
  • ---------------------------------
  • entity NAND2X3 is
  • port (
  • a, b, c, d: in std_logic;
  • q: out std_logic
  • );
  • end NAND2X3;
  • ---------------------------------
  • architecture ddd of NAND2X3 is
  • begin
  • q <= (a and b) or (c and d);
  • end ddd;

Кстати говоря, язык VHDL позволяет одному интерфейсу иметь несколько архитектурных тел. Давайте возьмем пример из реального проекта — компонент управления светодиодным индикатором ошибки. Допустим у нас есть два сигнала (ERR1, ERR2), информирующие об ошибке всего проектируемого устройства и для каждого сигнала есть разрешение индикации (EN1, EN2). Светодиод должен загораться по любому разрешенному сигналу. Для проектирования воспользуемся компонентом NAND2X3. На данном этапе попробуем две архитектуры этого компонента, а дальше выберем наиболее оптимальную. Посмотрите на следующий VHDL-код:

  • --------------------------------------------------
  • -- VHDL код для трех компонентов NAND2
  • --------------------------------------------------
  • library ieee;
  • use ieee. std_logic_1164.all;
  • ---------------------------------
  • entity NAND2X3 is
  • port (
  • a, b, c, d: in std_logic;
  • q: out std_logic
  • );
  • end NAND2X3;
  • ---------------------------------
  • architecture bbb of NAND2X3 is
  • signal s1, s2: std_logic;
  • component NAND2
  • port (
  • a, b: in std_logic;
  • c: out std_logic);
  • end component;
  • begin
  • D1: NAND2 port map (a => a, b => b, c => s1);
  • D2: NAND2 port map (a => c, b => d, c => s2);
  • D3: NAND2 port map (a =>s1, b =>s2, c =>q);
  • end bbb;
  • ---------------------------------
  • architecture ddd of NAND2X3 is
  • begin
  • q <= (a and b) or (c and d);
  • end ddd;
  • --------------------------------------------------
  • -- VHDL код управления индикацией
  • --------------------------------------------------
  • library ieee;
  • use ieee. std_logic_1164.all;
  • ---------------------------------
  • entity ERR_LED is
  • port (
  • ERR1, EN1, ERR2, EN2: in std_logic;
  • LED: out std_logic);
  • end ERR_LED;
  • ---------------------------------
  • architecture arch of ERR_LED is
  • --component NAND2X3 is
  • --port (
  • --a, b, c, d: in std_logic;
  • --q: out std_logic);
  • --end component;
  • signal serror: std_logic;
  • begin
  • C1: entity WORK. NAND2X3 (bbb) port map(a => ERR1, b => EN1, c => ERR2, d => EN2, q => serror);
  • LED <= '0' when serror = '1' else 'Z';
  • end arch;

Синтезатор Leonardo Spectrum для микросхемы MAX3128ATC144 из данного VHDL-кода сгенерирует схему, показанную на рисунке 13.

Рисунок 13

Если в выражение WORK. NAND2X3 (bbb) подставить архитектуру ddd, то Leonardo все равно сгенерирует такую же схему, можете проверить. Повторю, что данная схема получилась для конкретной микросхемы (технологии) ПЛИС. Другое дело если Вы пишите VHDL-модель, тогда разные архитектуры могут вести себя по-разному. Кстати, обратите внимание, что объявление компонента NAND2X3 закомментировано. VHDL допускает не объявлять используемые компоненты, если они определены ранее и подключаются записью типа:

  • NAME: entity WORK. NAME_ENTITY (NAME_ARCHITECTURE)
  • generic map(…)
  • port map(…);

ПРОЦЕССЫ
… процесс пошел.

Давайте продолжим развивать наш VHDL-проект. Пусть наше устройство будет программируемым, и сигналами разрешения/запрещения индикации ошибки будет управлять внешний процессор через управляющий регистр RegUpr. Итак, нам нужен регистр. Давайте напишем VHDL-интерфейс нашего регистра.

  • ---------------------------------
  • -- VHDL-интерфейс регистра
  • ---------------------------------
  • library ieee;
  • use ieee. std_logic_1164.all;
  • ---------------------------------
  • entity RegUpr is
  • port (
  • wr, cs: in std_logic;
  • addr: in std_logic_vector (1 downto 0);
  • datain: in std_logic_vector (7 downto 0);
  • en1, en2: out std_logic
  • );
  • end RegUpr;
  • ---------------------------------

Теперь нам нужно сделать так, чтобы регистр реагировал на определенную комбинацию сигналов rw, cs и addr. В языке VHDL добиться этого можно несколькими способами. Один из способов это организовать процесс. В общем виде VHDL-процесс записывается так:

  • LABEL: process (лист чувствительности)
  • -- область деклараций
  • begin
  • --VHDL операторы
  • end process;

Язык VHDL допускает процессы без меток, а также отсутствие листа чувствительности при наличии в теле процесса хотябы одного оператора wait. C учетом вышесказанного, напишем архитектуру регистра на VHDL:

  • -----------------------------------
  • -- VHDL-архитектура регистра
  • -----------------------------------
  • architecture arch of RegUpr is
  • signal regupr: std_logic_vector (7 downto 0);
  • begin
  • -------------------------------
  • -- VHDL-процесс регистра
  • -------------------------------
  • process (wr, cs, addr)
  • begin
  • if (cs = '1' and wr = '0') then
  • case addr is
  • when „01“ => regupr <= datain;
  • when others => null;
  • end case;
  • end if;
  • end process;
  • en1 <= regupr (0);
  • en2 <= regupr (1);
  • end arch;

Теперь осталось соединить наш VHDL-регистр со схемой формирования ошибки и у нас получится работоспособное программируемое устройство.

  • --------------------------------------------------
  • -- VHDL код управления индикацией
  • --------------------------------------------------
  • library ieee;
  • use ieee. std_logic_1164.all;
  • ---------------------------------
  • entity ERR_LED is
  • port (
  • ERR1, ERR2, wr, cs: in std_logic;
  • addr: in std_logic_vector (1 downto 0);
  • data: in std_logic_vector (7 downto 0);
  • LED: out std_logic
  • );
  • constant GND: std_logic:='0';
  • constant TRI: std_logic:='Z';
  • end ERR_LED;
  • ---------------------------------
  • architecture led_arch of ERR_LED is
  • signal serror, en1, en2: std_logic;
  • begin
  • C0: entity WORK. RegUpr (arch) port map(wr => wr, cs => cs, en1 => en1, en2 => en2, datain => data, addr => addr);
  • C1: entity WORK. NAND2X3 (bbb) port map(a => ERR1, b => en1, c => ERR2, d => en2, q => serror);
  • LED <= GND when serror = '1' else TRI;
  • end led_arch;

Синтезатор Leonardo Spectrum для микросхемы MAX3128ATC144 из данного VHDL-кода сгенерирует схему, показанную на рисунке 14.

Рисунок 14

Все хорошо, но смущает сигнал выбора устройства. Конечно без него никак, потому как адресная шина процессора наверняка больше, чем наши два разряда. Но есть вопрос, кто его формирует. Конечно, многие современные процессоры могут сами формировать несколько CS для разных областей адресного пространства, но мы в учебных целях будем считать, что дешифрация адреса возложена на периферию. Итак, нам нужен дешифратор адреса. Пусть адрес стробируется сигналом ALE. Тогда нам надо как-то ловить этот строб. Вот таким вот путем мы и подошли к атрибутам VHDL, которые и рассмотрим в следующем разделе.

АТРИБУТЫ
… а компьютер считался атрибутом буржуазного общества.

Атрибуты — это различные характеристики объектов VHDL. Они делятся на предопределенные и пользовательские. Мы рассмотрим и те и другие, хотя использование пользовательских атрибутов очень обременительно и мало что дает, но может кому-то и понравится.

Предопределенные атрибуты делятся на три группы — атрибуты сигналов, атрибуты массивов и атрибуты типов. Давайте мы их так и будем рассматривать. В таблице 1 приведены атрибуты сигналов, в таблице 2 — массивов, в таблице 3 — типов данных.

Таблица 1

АтрибутОписание
S’active TRUE, если было присвоение, но текущее значение еще прежнее
S’delayed (t) Значение сигнала, существовавшее на время t перед вычислением данного атрибута
S’event TRUE, если происходит изменение сигнала
S’last_active Время от последнего присвоения значения сигналу до момента вычисления атрибута
S’last_event Время от последнего изменения сигнала до момента вычисления атрибута
S’last_value Последнее присвоенное сигналу значение
S’stable (t) TRUE, если не происходило изменение сигнала в течение времени t
S’transaction TRUE, если происходит очередное присвоение значения сигналу
S’quiet FALSE, если было присвоение, но текущее значение еще прежнее

Таблица 2

АтрибутОписание
A’left (N) Левая граница N-го индекса массива А
A’right (N) Правая граница N-го индекса массива А
A’high (N) Верхняя граница N-го индекса массива А
A’low (N) Нижняя граница N-го индекса массива А
A’range (N) Диапазон N-го индекса массива А
A’reverse_range (N) Обратный диапазон N-го индекса массива А
A’length (N) Длина диапазона N-го индекса массива А

Таблица 3

АтрибутОписание
T’base Базовый тип данных
T’left Левая граница значений T
T’right Правая граница значений T
T’high Верхняя граница значений T
T’low Нижняя граница значений T
T’pos (X) Позиция значения Х в наборе значений Т
T’val (N) Значение элемента в позиции N набора значений Т
T’succ (X) Значение в наборе значений T, на одну позицию большее X
T’pred (X) Значение в наборе значений T, на одну позицию меньшее X
T’succ (X) Значение в наборе значений T, на одну позицию вправо от X
T’pred (X) Значение в наборе значений T, на одну позицию влево от X

Несколько слов о пользовательских атрибутах в языке VHDL. Раньше было модно назначать пины микросхемы прямо в VHDL-коде через пользовательские атрибуты, и компилятор брал эти значения и минуя синтезатор передавал их разводчику. Но сейчас такое почти нигде не практикуется. Во первых Вы можете не знать на этапе написания VHDL-кода, какую микросхему примените, во вторых сам компилятор может не знать, каким разводчиком Вы воспользуетесь, потому что серьезные разработчики практически не пользуются программами „все в одном флаконе“, да и нет сейчас таких фирм, чтобы сами все делали, все равно синтезаторы от одной фирмы, компиляторы от другой, разводчики от третьей, а симуляторы от четвертой, все это в одну оболочку собрали и обозвали, к примеру, КВАРТУС II.

Но в качестве примера такие атрибуты подойдут в самый раз:

  • entity example is
  • port (res, clk: in std_logic;
  • data_in: in std_logic_vector (3 downto 1);
  • data_out: out std_logic_vector (3 downto 1)
  • );
  • attribute pinnum: string;-- Определили атрибут pinnum
  • attribute pinnum of res signal is „5“;-- Здесь и далее назначаем пины
  • attribute pinnum of clk signal is „9“;
  • attribute pinnum of data_in signal is „11, 12, 13“;
  • attribute pinnum of data_out signal is „18, 20, 64″;
  • end example;

Ну вот вроде и все про атрибуты в языке VHDL.

Так вот, чтобы поймать строб сигнала, нам как раз подходит атрибут S’event. Посмотрите в таблице 1. S’event дает TRUE, если происходит изменение сигнала, а это ни что иное как фронт сигнала (строб). Такой атрибут любой синтезатор воспримет правильно и подключит его ко входу синхронизации тригера, что нельзя сказать о других атрибутах, которые в основном используются для моделирования или носят вспомогательные функции.

Итак, мы выбрали атрибут, но этого мало. Еще нужно указать какой фронт ALE мы будем ловить. Для определенности, пусть нам нужно срабатывание по переднему фронту — ----/----. Тогда это будет соответствовать выражению clk’event and clk = '1'. Только не будем торопиться писать код, это мы сделаем в следующем разделе.

ПОДПРОГРАММЫ VHDL

В VHDL используется два вида подпрограмм — это функции и процедуры. В библиотеке std_logic_1164 описаны различные функции, в том числе и rising_edge ()/falling_edge () определение которых выглядит так:

  • function rising_edge (signal s: std_ulogic) return boolean is
  • begin
  • return (s'event and s = '1');
  • end;
  • function falling_edge (signal s: std_ulogic) return boolean is
  • begin
  • return (s'event and s = '0');
  • end;

А так как мы используем библиотеку std_logic_1164, то спокойно можем задействовать эти функции. Теперь давайте напишем наш дешифратор 16-ти разрядного адреса:

  • library ieee;
  • use ieee. std_logic_1164.all;
  • ---------------------------------
  • entity dec is
  • port (res, ale: in std_logic;
  • addr: in std_logic_vector (15 downto 2);
  • cs: out std_logic);
  • end dec;
  • ---------------------------------
  • architecture dec_arch of dec is
  • begin
  • process (res, ale)
  • begin
  • if (res = '1') then
  • cs <= '0';
  • elsif rising_edge (ale) then
  • if (addr = „11000011110000″) then -- Это наше адресное пространство
  • cs <= '1';
  • else
  • cs <= '0';
  • end if;
  • end if;
  • end process;
  • end dec_arch;

У нас получился работоспособный дешифратор адреса, а теперь приведем пример ошибки, которую часто допускают начинающие разработчики:

  • architecture dec_arch of dec is
  • signal addr_valid: std_logic_vector (15 downto 2);
  • begin
  • process (res, ale)
  • begin
  • if (res = '1') then
  • addr_valid <= „00000000000000″;
  • elsif rising_edge (ale) then
  • addr_valid <= addr;
  • end if;
  • if (addr_valid = „11000011110000″) then -- Это наше адресное пространство
  • cs <= '1';
  • else
  • cs <= '0';
  • end if;
  • end process;
  • end dec_arch;

Для начинающих и поясним. Ошибка не в VHDL-коде, такой код пропустит любой САПР без запинки. Но дело в том, что в момент переключения по стробу addr_valid будет принимать всевозможные „случайные“ значения, так называемый „дребезг“. И есть шанс, что в какой-то момент addr_valid примет значение „11_2002_1111_2002“, тогда cs примет валидное значение и неизвестно что будет с нашей схемой обработки ошибки. Подобные ошибки очень коварны, они не всегда о себе дают знать, моделирование может не выявить их, даже в железе может все работать нормально. Но изменение температурного режима, переход на другой тип микросхемы и т. п. может сразу привести к негативному результату.

Давайте продолжим развивать наш проект. Было бы расточительным, если бы наш дешифратор адреса выбирал бы только одно устройство. Пускай он еще выбирает внешнее статическое ОЗУ 32×8, согласно рисунку 15.

Рисунок 15



Давайте перепишем наш дешифратор:

  • library ieee;
  • use ieee. std_logic_1164.all;
  • ---------------------------------
  • entity dec is
  • port (res, ale, cs: in std_logic;
  • addr: in std_logic_vector (15 downto 5);
  • cs_er, cs_ram: out std_logic);
  • end dec;
  • ---------------------------------
  • architecture dec_arch of dec is
  • begin
  • process (res, ale)
  • signal s_cs_ram, s_cs_er: std_logic;
  • begin
  • if (res = '1') then
  • s_cs_ram <= '0';
  • s_cs_er <= '0';
  • elsif rising_edge (ale) then
  • if (addr = „00000000000″) then -- Адресное пространство ОЗУ
  • s_cs_ram <= '1';
  • s_cs_er <= '0';
  • elsif (addr = „00000000001″) then -- Адресное пространство устройства
  • s_cs_ram <= '0';
  • s_cs_er <= '1';
  • else
  • s_cs_ram <= '0';
  • s_cs_er <= '0';
  • end if;
  • end if;
  • end process;
  • cs_ram <= s_cs_ram and cs;
  • cs_er <= s_cs_er and cs;
  • end dec_arch;

На рисунке 16 показано, как данный VHDL-код был воспринят квартусом. Обратите внимание, что код elsif rising_edge (ale) then реализован в виде входов синхронизации триггеров, на которые заводится сигнал ale, чего мы в принципе и хотели.

Рисунок 16



Сделаем соответствующие изменения в нашем устройстве, подключив дешифратор:

  • library ieee;
  • use ieee. std_logic_1164.all;
  • ---------------------------------
  • entity ERR_LED is
  • port (
  • -- Сигналы управления и шины процессора
  • res, wr, oe, cs, ale: in std_logic;
  • addr: in std_logic_vector (15 downto 0);
  • data: in std_logic_vector (7 downto 0);
  • -- Сигналы управления ОЗУ
  • cs_ram, wr_ram, oe_ram: out std_logic;
  • -- Сигналы управления индикацией
  • ERR1, ERR2: in std_logic;
  • LED: out std_logiс
  • );
  • constant GND: std_logic:='0';
  • constant TRI: std_logic:='Z';
  • end ERR_LED;
  • ---------------------------------
  • architecture led_arch of ERR_LED is
  • signal serror, en1, en2, s_cs_er: std_logic;
  • signal addr_h: std_logic_vector (15 downto 5);
  • signal addr_l: std_logic_vector (1 downto 0);
  • begin
  • addr_h <= addr (15 downto 5);
  • addr_l <= addr (1 downto 0);
  • C0: entity WORK. dec (dec_arch) port map(res => res, ale => ale, cs => cs, addr => addr_h, cs_er => s_cs_er, cs_ram => cs_ram);
  • C1: entity WORK. RegUpr (arch) port map(wr => wr, cs => s_cs_er, en1 => en1, en2 => en2, datain => data, addr => addr_l);
  • C2: entity WORK. NAND2X3 (bbb) port map(a => ERR1, b => en1, c => ERR2, d => en2, q => serror);
  • LED <= GND when serror = '1' else TRI;
  • wr_ram <= wr;
  • oe_ram <= oe;
  • end led_arch;

Для того чтобы смоделировать работу ПЛИС в схеме, показанной на рисунке 15, нам нужно иметь модели процессора и ОЗУ. Но если модель ОЗУ мы легко можем написать на VHDL сами, то модель процессора написать достаточно долго и трудно, если у Вас есть деньги, то модель можно купить у производителя. Но по большому счету, чтобы проверить работу ПЛИС, нам необязательно иметь модель процессора, достаточно имитировать его сигналы в соответствии с временными диаграммами, взятыми из DATASHEET. Реальным DATASHEET мы пользоваться не будем, а воспользуемся упрощенными временными диаграммами (рисунки 17, 18).

Рисунок 17



Рисунок 18



Имитатор процессора мы напишем на VHDL в виде процедуры.

  • procedure IM_CPU (res, w: in std_logic; ale, wr, oe, cs: out std_logic; A: in std_logic_vector (15 downto 0); addr: out std_logic_vector (15 downto 0); D: in std_logic_vector (7 downto 0); data: out std_logic_vector (7 downto 0)) is
  • begin
  • if (res = '1') then -- Инициализация
  • ale := '0'; cs := '0'; wr := '0'; oe := '0';
  • addr := x„2002“;
  • data := „ZZZZZZZZ“;
  • elsif (w = '0') then -- Цикл чтения
  • addr := A;
  • ale := '0'; wr := '0'; oe := '0'; cs := '0';
  • data := „ZZZZZZZZ“;
  • wait for 10 ns;
  • ale := '1';
  • wait for 15 ns;
  • ale := '0';
  • wait for 20 ns;
  • cs := '1';
  • wait for 10 ns;
  • oe := '1';
  • wait for 30 ns;
  • oe := '0';
  • wait for 15 ns;
  • cs := '0';
  • else -- Цикл записи
  • addr := A;
  • ale := '0'; wr := '1'; oe := '0'; cs := '0';
  • data := „ZZZZZZZZ“;
  • wait for 10 ns;
  • ale := '1';
  • wait for 15 ns;
  • ale := '0';
  • wait for 30 ns;
  • data := D;
  • wait for 10 ns;
  • cs := '1';
  • wait for 15 ns;
  • cs := '0';
  • wait for 10 ns;
  • data := „ZZZZZZZZ“;
  • end if;
  • end;

Теперь, нам достаточно в тесте вызывать процедуру IM_CPU с соответствующими параметрами, чтобы получить необходимые циклы процессора. Думается, что тут даже пояснять ничего не надо, все достаточно тривиально — просто расписаны диаграммы циклов, в соответствии с рисунками 17 и 18. Можно было бы использовать after, но тогда пришлось бы высчитывать каждый последующий интервал времени для каждого сигнала, а так мы просто переложили диаграммы в VHDL-код. Ну тут, как говорится, дело вкуса. Следует отметить, что это несинтезируемый VHDL-код, этот код мы можем использовать только для моделирования.

БИБЛИОТЕКИ VHDL
… библиотека — учреждение, собирающее и хранящее произведения печати и письменности для общественного пользования

Как и в языках программирования, в VHDL имеется возможность подключать различные библиотеки с заранее написанным кодом. Библиотеки могут быть скомпилированными для определенного САПР или быть в исходных кодах. Мы уже использовали библиотеки STD (встроенная в VHDL), IEEE (конкретно std_logic_1164), а также все нами написанное по умолчанию компилируется в библиотеку WORK. Кроме того, многие САПР подключают свои библиотеки примитивов и у нас тут не все гладко. Многие САПР возмутятся, что мы определили элемент NAND2, так как в их библиотеке примитивов уже есть такое определение. Тут можно просто переименовать NAND2 в MY_NAND2 или задействовать готовый элемент.

Мы можем создавать свои библиотеки, чем и займемся в этом разделе. У нас конечно не очень много VHDL-кода и вообще говоря создавать библиотеку для нашего проекта нецелесообразно, так как все прекрасно и удобно размещается в одном файле. Но чтобы разобраться, что к чему, давайте создадим VHDL-библиотеку. В общем виде VHDL-библиотека выглядит так:

  • package package_name is
  • -- Объявления типов и подтипов
  • -- Объявления сигналов и констант
  • -- Объявления функций, процедур и компонентов
  • end package_name;
  • ----------------------------------------
  • package body package_name is
  • -- Определения функций
  • -- Определения процедур
  • end package_name;

Если нет необходимости определять функции, процедуры и компоненты, то можно обойтись без package body.

Итак, давайте напишем на VHDL библиотеку, в которую поместим имитатор процессора и модель ОЗУ. При этом, для упрощения кода воспользуемся библиотекой переопределений GLOBAL EASY.

  • library ieee, EASY;
  • use ieee. std_logic_1164.all;
  • use ieee. std_logic_unsigned.all;
  • use EASY. GLOBAL.all;
  • entity mem is
  • port(addr: in std5;
  • cs, wr, oe: in std1;
  • data: inout std8);
  • end mem;
  • ----------------------------------------
  • architecture behave of mem is
  • begin
  • memproc: process(addr, cs, wr)
  • type t_mem is array(0 to 31) of std8;
  • variable mem_data: t_mem;
  • begin
  • data <= TRI8;
  • if (cs = '1') then
  • if (wr = '0' and oe = '1') then
  • data <= mem_data (CONV_INTEGER (addr)) after 1 ns;
  • elsif (wr = '1') then
  • mem_data (CONV_INTEGER (addr)) := data;
  • end if;
  • else
  • data <= TRI8 after 1 ns;
  • end if;
  • end process;
  • end behave;
  • ----------------------------------------
  • library ieee, EASY;
  • use ieee. std_logic_1164.all;
  • use EASY. GLOBAL.all;
  • package my_package is
  • procedure IM_CPU (res: in std1; constant w: in std1; signal ale, wr, oe, cs: out std1; constant A: in std16; signal addr: out std16; constant D: in std8; signal data: out std8);
  • component mem is
  • port(addr: in std5;
  • cs, wr, oe: in std1;
  • data: inout std8);
  • end component;
  • ----------------------------------------
  • component ERR_LED is
  • port (
  • res, wr, oe, cs, ale: in std1;
  • addr: in std16;
  • data: in std8;
  • cs_ram, wr_ram, oe_ram: out std1;
  • ERR1, ERR2: in std1;
  • LED: out std1
  • );
  • end component;
  • end my_package;
  • ----------------------------------------
  • package body my_package is
  • procedure IM_CPU (res, w: in std1; signal ale, wr, oe, cs: out std1; constant A: in std16; signal addr: out std16; constant D: in std8; signal data: out std8) is
  • begin
  • if (res = '1') then -- Инициализация
  • ale <= '0'; cs <= '0'; wr <= '0'; oe <= '0';
  • addr <= GND16;
  • data <= TRI8;
  • elsif (w = '0') then -- Цикл чтения
  • addr <= A;
  • ale <= '0'; wr <= '0'; oe <= '0'; cs <= '0';
  • data <= TRI8;
  • wait for 10 ns;
  • ale <= '1';
  • wait for 15 ns;
  • ale <= '0';
  • wait for 20 ns;
  • cs <= '1';
  • wait for 10 ns;
  • oe <= '1';
  • wait for 30 ns;
  • oe <= '0';
  • wait for 15 ns;
  • cs <= '0';
  • else -- Цикл записи
  • addr <= A;
  • ale <= '0'; wr <= '1'; oe <= '0'; cs <= '0';
  • data <= TRI8;
  • wait for 10 ns;
  • ale <= '1';
  • wait for 15 ns;
  • ale <= '0';
  • wait for 30 ns;
  • data <= D;
  • wait for 10 ns;
  • cs <= '1';
  • wait for 15 ns;
  • cs <= '0';
  • wait for 10 ns;
  • data <= TRI8;
  • end if;
  • end;
  • end my_package;

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

ТЕСТИРОВАНИЕ

Ну вот мы и подошли к заключительной фазе — тестирование проекта. В принципе у нас для этого все готово и остается только соединить все ранее написаное на VHDL в тестовую модель (test bench). В путь:

  • library ieee, EASY, work;
  • use ieee. std_logic_1164.all;
  • use ieee. std_logic_unsigned.all;
  • use EASY. GLOBAL.all;
  • use work. my_package.all; -- Присоединили нашу библиотеку
  • entity test_bench is
  • -- Пустой интерфейс
  • end test_bench;
  • ------------------
  • architecture behave of test_bench is
  • -- Тестовые сигналы
  • signal res, wr, oe, cs, ale, cs_ram, wr_ram, oe_ram, ERR1, ERR2, LED: std1;
  • signal addr: std16;
  • signal data: std8;
  • begin
  • -- Это наша ПЛИС (Unit Unde Test)
  • UUT1: ERR_LED port map (
  • res => res, wr => wr, oe => oe, cs => cs, ale => ale,
  • addr => addr, data => data,
  • cs_ram => cs_ram, wr_ram => wr_ram, oe_ram => oe_ram,
  • ERR1 => ERR1, ERR2 => ERR2, LED => LED);
  • -- Это наша память
  • UUT2: mem port map (
  • addr => addr (4 downto 0),
  • cs => cs_ram, wr => wr_ram, oe => oe_ram,
  • data => data);
  • process begin -- Лист чувствительности не нужен
  • res <= '1'; ERR1 <= '0'; ERR2 <= '0';
  • IM_CPU (res, '0', ale, wr, oe, cs, x"2002“, addr, x"00″, data); -- Вызываем имитатор
  • wait for 100 ns;
  • res <= '0';
  • -- Пишем в память
  • IM_CPU (res, '1', ale, wr, oe, cs, x"2002“, addr, x"00″, data);
  • wait for 10 ns;
  • IM_CPU (res, '1', ale, wr, oe, cs, x"001F“, addr, x"1F“, data);
  • wait for 10 ns;
  • -- Читаем из памяти
  • IM_CPU (res, '0', ale, wr, oe, cs, x"2002“, addr, x"00″, data);
  • wait for 10 ns;
  • IM_CPU (res, '0', ale, wr, oe, cs, x"001F“, addr, x"1F», data);
  • wait for 10 ns;
  • ERR1 <= '1'; -- Выдаем сигнал ошибки
  • wait for 100 ns;
  • IM_CPU (res, '0', ale, wr, oe, cs, x"0021″, addr, x"3″, data); -- Разрешаем индикацию
  • wait;
  • end process; -- Конец теста
  • end behave;

Конечно, можно написать более мощный тест, на наша задача была рассмотреть принцип написания VHDL-моделей, и мы это сделали. Следующим шагом надо зарядить весь проект в какой-нибудь симулятор и наблюдать временные диаграмы. Этот шаг Вы можете освоить самостоятельно, т. к. освоение симуляторов не входило в наши планы (может в будущем…).

ЗАКЛЮЧЕНИЕ

Вот мы и закончили наш проект. Кому интересно, тот может скачать архив написанного нами VHDL-проекта. Еще раз следует отметить, что все изложенное здесь не является учебником по VHDL, а лишь является пособием. Полученный проект является небольшим кусочком реального большого проекта, который успешно работает на объектах нашей Родины. Но для написания большого проекта, целесообразно разбить его на несколько функциональных узлов. Очень часто такие узлы на VHDL описывают разные люди, а затем эти узлы объединяются в одно устройство.

Следует отметить еще один момент. При написании этой странички, в VHDL-код могла закрасться ошибка. Автор будет очень признателен если Вы сообщите об этом по адресу mail@allhdl.ru.

ПРИЛОЖЕНИЕ А

В таблице 4 приведен список зарезервированных слов языка VHDL.

Таблица 4

abs disconnect label port srl
access downto library postponed subtype
after linkage procedure
alias else literal process then
all elsif loop protected to
and end pure transport
architecture entity map type
array exit mod range
assert record unaffected
attribute file nand register units
for new reject until
begin function next rem use
block nor report &nbsp
body generate not return variable
buffer generic null rol &nbsp
bus group ror wait
&nbsp guarded of &nbsp when
case &nbsp on select while
component if open severiti with
configuration impure or shared &nbsp
costant in others signal xnor
inertial out sla xor
inout sll
is package sra
ПРИЛОЖЕНИЕ B. ПРИМЕРЫ НА VHDL

Пример синхронного регистра на VHDL:

  • -- по переднему фронту
  • process (CLK)
  • begin
  • if (rising_edge (CLK)) then
  • REG <= data;
  • end if;
  • end process;
  • -- или по заднему фронту
  • process (CLK)
  • begin
  • if (falling_edge (CLK)) then
  • REG <= data;
  • end if;
  • end process;

Пример синхронного регистра на VHDL с асинхронным сбросом:

  • process (CLK, RESET)
  • begin
  • if RESET = '0' then
  • REG <= '0';
  • elsif (rising_edge (CLK)) then
  • REG <= data;
  • end if;
  • end process;

Пример синхронного регистра на VHDL с асинхронным сбросом и разрешением синхро-импульсов:

  • process (CLK, RESET, CE)
  • begin
  • if RESET = '0' then
  • REG <= '0';
  • elsif (rising_edge (CLK)) then
  • if CE = '1' then
  • REG <= data;
  • end if;
  • end if;
  • end process;

Пример защелки на VHDL с асинхронным сбросом:

  • process (RESET, EN)
  • begin
  • if RESET = '0' then
  • LATCH <= '0';
  • elsif EN = '1' then
  • LATCH <= data;
  • end if;
  • end process;

Пример сумматора на VHDL:

  • library ieee;
  • use ieee. std_logic_1164.all;
  • use ieee. numeric_std.all;
  • entity signed_adder is
  • generic
  • (
  • DATA_WIDTH: natural := 8
  • );
  • port
  • (
  • a: in signed ((DATA_WIDTH-1) downto 0);
  • b: in signed ((DATA_WIDTH-1) downto 0);
  • result: out signed ((DATA_WIDTH-1) downto 0)
  • );
  • end entity;
  • architecture rtl of signed_adder is
  • begin
  • result <= a + b;
  • end rtl;
E-mailmail@allhdl.ru
© allhdl.ru, 2007 - 2015