Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Верификация цифровых устройств. Курс лекций. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
06.09.2026
Размер:
1 Мб
Скачать
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 –