Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Верификация цифровых устройств. Курс лекций. Учебное пособие
.pdf
2.2. Язык SystemVerilog
Для описания как моделей устройств, так и их тестового окружения применяются языки описания аппаратуры – HDL. До недавнего времени двумя самыми распространенными языками являлись
VHDL и Verilog. Много копий было сломано в спорах, какой язык
лучше, но однозначного ответа, безусловно, нет. Однако оба этих
языка устаревают, и последнее десятилетие все большую популярность приобретает новый язык – SystemVerilog. И если для описания аппаратуры по-прежнему широко используются VHDL и
Verilog, то при разработке тестов SystemVerilog достаточно давно
выбился в лидеры.
Во многом это связанно с тем, что, с одной стороны, он обратно
совместим со своим предшественником – языком Verilog, на основе
которого он разработан. С другой стороны, данный язык внес много нового, в частности поддержку объектно-ориентированной модели программирования, что делает его более эффективным инструментом при разработке тестового окружения с высокими уровнями абстракции.
Третьим фактором, влияющим на его популярность, является
схожий с языком С++ синтаксис, что упрощает переход на него
подавляющего большинства программистов.
Язык SystemVerilog, как и все остальные HDL языки, имеет два
подмножества – синтезируемое и несинтезируемое. Первое может
использоваться для описания как тестового окружения, так и моделей устройств. Несинтезируемая часть языка может использоваться
только для описания тестового окружения. Поскольку данный курс
посвящен верификации, то основной упор будет сделан на второе
подмножество.
2.2.1. Типы данных
SystemVerilog – типизированный язык, т.е. все переменные в
нем имеют заданный тип данных. Так как язык обратно совместимый с Verilog, то он поддерживает все типы данных, которые были
в нем, где видим два базовых – wire и reg. Они могут принимать
четыре возможных значения – 0,1,Z,X и типы integer, realtime и
time.
– 21 –

У новичков в языке Verilog всегда возникали трудности с пониманием разницы между типами wire и reg. Тип wire используется
для соединения элементов и не может хранить значения; reg используется для последовательной логики и может хранить значения
во времени.
Первым новым типом данных в SystemVerilog является тип
logic. Он является своего рода объединением типов wire и reg. Этот
тип данных также допускает четыре состояния и может использоваться везде, где ранее использовались типы wire и reg, т.е., изучая
SystemVerilog, нет необходимости выбирать между типами.
Но есть одно исключение: logic не поддерживает силу присваивания, как wire. Это означает, что переменные logic должны всегда
иметь только один источник сигнала.
Также в SystemVerilog несколько новых типов данных с двумя
состояниями. Эти типы призваны ускорить моделирование, так как
операции с ними выполняются быстрее, чем с типами с четырьмя
состояниями из Verilog.
1. Bit – тип описания одиночного бита.
2. Int – 32-разрядный знаковый тип данных.
3. Int unsigned – 32-разрядный беззнаковый тип данных.
4. Byte – 8-разрядный знаковый тип.
5. Shortint – 16-разрядный знаковый тип.
6. Longint – 64-разрядный знаковый тип.
Надо всегда помнить, что новые типы в большинстве своем знаковые, иначе могут быть ошибки при их неверном использовании.
При необходимости их можно перевести в беззнаковые с помощью
ключевого слова unsigned.
Следует быть осторожными при соединении типов с двумя и четырьмя состояниями. При таком переходе существует вероятность
потерять данные – X или Z из типа с четырьмя состояниями преобразуются в 0 или 1, и тест не узнает о наличии ошибок.
$isunknown() – функция, которая возвращает 1, если хотя бы один
бит в выражении имеет X или Z.
Еще одно нововведение языка SystemVerilog – перечисляемые
типы данных. Синтаксис в работе с ними аналогичен С++. Чтобы
создать переменную перечисляемого типа, надо использовать ключевое слово enum. Пример ниже создает тип данных для описания
состояний конечного автомата:
– 22 –

enum {IDLE,REQ,RESP} states;
Можно также создать свой собственный перечисляемый тип
данных и использовать его для объявления переменных:
typedef enum {IDLE,REQ,RESP} states_t;
states_t state;
2.2.2. Массивы
SystemVerilog поддерживает несколько видов массивов:
1. Статические.
2. Динамические.
3. Очереди.
4. Ассоциативные.
Статические – массивы с заранее известной длиной. Для объявления такого массива можно указать верхнюю и нижнюю границы, или только верхнюю – тогда нижняя будет принята за 0:
int lo_hi [0:15];
intc_style [16];
Оба массива идентичны и содержат по 16 переменных типа int.
Возможно создание многомерных массивов. В этом случае размерности указываются последовательно в квадратных скобках:
int array2 [0:7][0:3];
int array3 [8][4];
Обращение к элементам массива в таком случае происходит с
последовательным указанием индексов в измерениях, аналогично
языку С++.
Если программа попробует обратиться за пределы массива, то в
ответ на чтение будет возвращаться значение по умолчанию для
данного типа. Для типов с четырьмя состояниями это Х, для типов
с двумя состояниями – 0.
В SystemVerilog массивы могут быть упакованные и неупакованные. Массивы, рассмотренные выше, – неупакованные. Это
означает, что синтезатор может сохранять их элементы в памяти
непоследовательно. Как правило, происходит выравнивание до
32 бит, т.е. массив
bytea [3]; будет занимать три 32-битных слова,
– 23 –

несмотря на то, что это байты. Это сделано для ускорения работы
моделирования.
Для создания упакованного массива его размеры должны указываться перед именеми только в формате старший-младший разряды. Следующий массив будет расположен в трех последовательных
байтах:
byte [3:0]a;
Также можно совмещать упакованные и неупакованные массивы.
Второй тип – динамические массивы. В отличие от статических размер динамического массива определяется в процессе моделирования. Следующий листинг иллюстрирует допустимые операции с динамическими массивами:
int dyn[],d2[]; // (1)
...
initial begin
dyn =new[5]; // (2)
foreach(dyn[j]) dyn[j]= j; // (3)
d2 = dyn; // (4)
d2[0]= 5; // (5)
$display(dyn[0],d2[0]); // (6)
dyn =new[20](dyn); // (7)
dyn =new[100]; // (8)
dyn.delete(); // (9)
end
В строке (1) заранее объявляются два динамических массива.
Изначально их длина равна 0. В строке (2) под массив выделяется
место и в (3) он заполняется значениями. Строка (4) иллюстрирует
копирование массива (а не указателя, как в С++). (5) и (6) проверяют, что мы работаем с копией. В выводе должны отобразиться 0 и
5. Строка (7) – пример изменения размера массива и копирования в
него старого значения; (8) – изменение размера без копирования.
Последняя строка – удаление всех элементов массива.
Третий тип массивов – очереди. Тип данных «очередь» объединяет лучшее из связанного списка и массива. Как и в связанном
списке, можно добавлять или удалять элементы в любом месте
очереди, без снижения производительности динамического массива, который должен выделить новый массив и скопировать все со-
– 24 –

держимое. Как и в массиве, можно получить прямой доступ к любому элементу с индексом без дополнительных затрат связанного
списка на переход по предыдущим элементам.
Очередь объявляется с помощью квадратных скобок, содержащих знак доллара: [$]. Элементы очереди пронумерованы от 0 до $.
Листинг ниже показывает, как можно добавлять и удалять значения
из очереди, используя методы.
int j = 1,
q2[$]={3,4},
q[$]={0,2,5}; // (1) {0,2,5}
initial begin
q.insert(1, j); // (2) {0,1,2,5}
q.insert(3, q2); // (3) {0,1,2,3,4,5}
q.delete(1); // (4) {0,2,3,4,5}
q.push_front(6); // (5) {6,0,2,3,4,5}
j = q.pop_back; // (6) {6,0,2,3,4} j = 5
q.push_back(8); // (7) {6,0,2,3,4,8}
j = q.pop_front; // (8) {0,2,3,4,8} j = 6
foreach(q[i]) $display(q[i]); // (9)
q.delete(); // (10)
end
Строка (1) – создание очереди из трех элементов; (2) и (3) –
вставка элемента и второй очереди; (4) – удаление элемента с 1 позиции; (5) – (8) – вставка и извлечение элементов в начало и конец
очереди. Эти операции выполняются быстро. Строка (9) – работа с
очередью по индексам.
Элементы очереди хранятся в смежных местах, и поэтому эффективно выдвигать и вставлять элементы в начало и конец. Это
занимает фиксированное время независимо от размера очереди.
Добавление и удаление элементов в середине очереди требует
смещения данных, чтобы освободить место. Это время растет линейно с размером очереди.
Последний тип массивов – ассоциативные. Ассоциативные
массивы хранят записи в разреженной матрице. Это означает, что,
хотя вы и можете адресовать очень большое адресное пространство, SystemVerilog выделяет память для элемента только при записи в него. Ассоциативный массив может быть сохранен симулятором в виде дерева или хеш-таблицы. Эти издержки допустимы,
– 25 –

когда нужно хранить массивы с широко разделенными значениями
индекса, такими как пакеты, индексированные с помощью 32разрядных адресов или 64-разрядных значений данных.
Ассоциативный массив объявляется с типом данных в квадратных скобках:
bit [63:0] assoc[int].
Для перемещения по элементам массива можно использовать
foreach, и функции first() и next() для доступа к первому
цикл
и следующим элементам массива;
exists() проверяет присутствие
элемента. При попытке прочитать несуществующий элемент вернется тип по умолчанию, так же как при выходе за границы статического массива.
– 26 –

Лекция 3. System Verilog – объектно-ориентированная часть языка
Введение
В данной лекции изучаются средства языка System Verilog, которые могут применяться при разработке тестового окружения. В
первую очередь рассмотрены функции и задачи, механизмы их вызова и работы. Далее речь идет о средствах языка, необходимых
методологии объектно-ориентированного программирования, –
классах. Рассмотрены механизмы создания классов и работы с ними. В заключительном разделе лекции описаны средства языка для
выполнения параллельных вычислений.
3.1. Функции и задачи
Verilog делает очень четкое различие между задачами и функциями. Самое важное отличие состоит в том, что задача может занимать время, а функция – нет. Функция не может иметь задержку,
# 100, операторы блокировки, такие как @ (posedge clock) или
wait (ready), или вызывать задачи. Кроме того, функция Verilog
должна возвращать значение, и значение должно использоваться.
SystemVerilog ослабляет эти правила. Функции в SystemVerilog
могут вызывать задачи, но только в потоке, порожденном оператором
fork ... join_none. Что такое потоки, будет рассмотрено
дальше.
Если есть задача SystemVerilog, которая не требует времени, ее
лучше сделать функцией
void, т.е. функцией, которая не возвраща-
ет значение, и ее можно будет вызывать из любой другой функции
или задачи. Для максимальной гибкости любая подпрограмма отладки должна быть функцией
void, а не задачей, чтобы ее можно
было вызывать из любой задачи или функции.
Если функция возвращает значение, но нет необходимости его
использовать, то результат функции можно преобразовать в
void(fun()). Некоторые симуляторы этого не требуют, и можно
void –
просто проигнорировать возвращаемое значение.
– 27 –

Синтаксис для объявления функций и задач в SystemVerilog стал
более приближен к синтаксисту языка С++, хотя также полностью
поддерживает синтаксис Verilog. Из особенностей можно отметить,
что для функций и задач с пустым списком аргументов необязательно ставить пустые скобки – () – при объявлении или вызове.
При объявлении типы аргументов могут не указываться – в этом
случае тип будет считаться аналогичным предыдущему аргумента,
а для первого по умолчанию –
input logic.
В SystemVerilog помимо значений в функции и задачи могут передаваться ссылки на аргументы. Для этого используется ключевое
слово
ref вместо направления.
Этот тип удобен для передачи массивов в качестве аргументов.
SystemVerilog позволяет передавать массивы в качестве аргумента
без
ref, но тогда массив копируется в стек, что является ресурсо-
емкой операцией, которую следует применять только для небольших массивов. При объявлении функции можно указать префикс
const перед ref – по аналогии с языком С++ это будет означать,
что внутри функции данный массив или переменная не должны
изменяться. При попытке их изменить выведется сообщение об
ошибке.
Для функций и задач при объявлении можно задавать значения
входных аргументов по умолчанию. Тогда при вызове эти аргументы можно не указывать – они примут значения по умолчанию.
3.2. ООП на System Verilog
Одним из важных нововведений языка System Verilog является
появление средств, необходимых методологии объектно-ориентированного программирования (ООП). ООП требует соблюдения
четырех основных принципов: абстракция, инкапсуляция, наследование и полиморфизм. В целом SystemVerilog достаточно строго
придерживается всех требований ООП: в нем есть возможность
создавать классы с открытыми и приватными полями, наследовать
методы, перегружать их. Есть возможность создавать виртуальные
функции и классы.
В данном курсе предполагается, что обучающиеся знакомы с
методологией ООП, поэтому будут рассмотрены только основные
моменты синтаксиса языка и отличия от С++.
– 28 –

3.2.1. Классы, данные и методы
Классы объединяют данные и методы, которые управляют этими данными. Для создания классов в SystemVerilog, как и в С++,
используется ключевое слово class. Пример класса:
class bus_operation;
bit[31:0] addr, cmd,data[32];
function void display;
$display("Addr: %h Data: %h ", addr, data);
endfunction: display
endclass: bus_operation
Класс в SystemVerilog можно определять как в программе, модуле, пакете, так и за их пределами. Каждый новый класс принято,
хотя и не обязательно, сохранять в отдельный файл. Когда количество файлов становится слишком большим, их можно сгруппировать в набор связанных классов (пакет) –
package. Такой пакет
можно скомпилировать отдельно от остальной части системы.
В SystemVerilog все классы являются динамическими объектами, т.е. их необходимо создавать в процессе моделирования. Переменные, у которых в качестве типа указаны классы, являются
только дескрипторами, изначально инициализированными null.
Пример создания нового экземпляра класса следующий:
bus_operationbo;
bo = new();
new () – имя функции-конструктора. По умолчанию эта функция
выделяет необходимую память под класс и инициализирует значения внутренних переменных: переменные с двумя состояниями
инициализируются 0, переменные с четырьмя состояниями – Х.
При необходимости можно определить свою функцию-конструктор, которая будет инициализировать переменные нужными значениями.
function void new();
addr = 10;
cmd = 15;
endfunction
– 29 –

Каждый объект имеет свои локальные переменные, которые не
используются совместно с другими объектами. Если есть два объекта bus_operation, то у каждого имеются свои переменные:
addr, crc и data. Но иногда нужна переменная, которая является
общей для всех объектов определенного типа. Например, может
быть необходимо иметь общий счетчик для количества созданных
экземпляров. В SystemVerilog можно создать статическую переменную внутри класса. Эта переменная является общей для всех
экземпляров класса, но ее область видимости ограничена классом.
Пример создания и инициализации статической переменной:
class bus_operation;
bit[31:0] addr, cmd, data[32];
static int count = 0;
function new();
count++;
endfunction
endclass
...
t1 = new(); // count=1
t2 = new(); // count=2
$display("%d",bus_operation::count); //выводе 2
Наравне со статическими переменными можно создавать и ста-
тические функции. Эти функции могут работать только с аналогичными переменными, а также могут вызываться даже тогда, когда не создано ни одного экземпляра класса:
static function void display_count ();
$display("%d",count);
endfunction
Пример вызова функции:
bus_operation::display_count();
Если есть необходимость разделить объявление и реализацию
функции, то можно использовать ключевое слово extern. В теле
класса объявляется прототип функции – ее имя и аргументы, и ставится слово
extern, а реализация выносится за границы класса и
– 30 –
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
