Модуль 2. Основы языка SystemVerilog
Язык SystemVerilog является расширением языка Verilog и предназначен в основном для проведения функциональной верификации моделей цифровой аппаратуры. Первый IEEE стандарт языка SystemVerilog появился в 2005 году, после чего прошло ещё два обновления. В настоящее время действующим стандартом является редакция 2012 г. [1]. Данный курс предполагает, что читатель уже знаком с одним из объектно-ориентированных языков программирования (например, C++) и языком описания аппаратуры Verilog, поэтому далее рассматриваются в основном нововведения появившиеся в стандарте SystemVerilog.
В SystemVerilog можно выделить два типа данных: цепи (связи или сигналы) и переменные. Цепи могут быть использованы для соединения модулей и могут назначаться с помощью оператора непрерывного назначения (assign). Цепи могут иметь несколько источников назначения.
Переменные также могут выполнять соединение блоков и могут назначаться с помощью оператора назначения (assign). Так же они могут назначаться в одном или нескольких процессах (операторы initial и always). Следует заметить, что если переменная назначается оператором assign или подключена к выходному порту, то она не может иметь иных источников назначения.
Целочисленные литералы могут быть заданы в двоичной, восьмеричной, десятичной или шестнадцатеричной форме. Размер (число разрядов или бит) и знак (знаковое или число без знака) также может быть задано. Знак нижнего подчеркивания может разделять разряды для улучшения читаемости кода. Кроме этого в SystemVerilog могут быть заданы литералы: числа с плавающей точкой, время, символьные строки, массивы и структуры. В таблице 2.1 представлено несколько примеров задания литералов различных типов.
Таблица 2.1 - Значения литералов
Литерал | Описание |
34 | 32-разрядное десятичное число |
-26 | Отрицательное десятичное число |
8’b1010_1101 | 8-разрядное двоичное число без знака |
8’so27 | 8-разрядное восьмеричное число со знака |
32 'sd15 | 32-разрядное знаковое десятичное число |
’hff | Безразмерное шестнадцатеричное число без знака |
8’bz | Эквивалентно записи числа 8'bzzzz_zzzz |
’x | Все разряды задаются равными x |
10ns | Задание времени 10 нс |
’{1, 2, 3} | Задание массива |
’{1, “Hello”, 3.14} | Задание структуры |
“Hello” | Строковый литерал (8 бит на один символ, всего 40 бит) |
Язык SystemVerilog включает несколько целочисленных типов данных, которые могут быть со знаком и без.
Двузначные типы, принимающие значения для каждого разряда из множества {0, 1}, к ним относятся : bit, byte, shortint, int и longint.
Четырехзначные типы, принимающие значения для каждого разряда из множества {0, 1, x, z} это: logic, reg, integer и time. Для типов bit, logic и reg можно задать размер (число разрядов). Остальные типы имеют фиксированную разрядность. Типы logic и reg являются взаимозаменяемыми, но для SystemVerilog предпочтительно использовать тип logic.
Для задания чисел с плавающей точкой используются переменные типа shortreal, real и realtime.
Для удобства написания программ в SystemVerilog используются также структуры, объединения и перечислимый тип (enum). А так же с помощью оператора typedef могут быть заданы пользовательские типы.
Таблица 2.1.1 - Декларации типов
Тип | Знаковый или Беззнаковый | 2 или 4 возможных значения для каждого разряда | Размер занимаемой памяти | Min/Max |
bit | Беззнаковый | 2 | 1 | 0 / 1 |
byte | Знаковый | 2 | 8 | -128/127 |
shortint | Знаковый | 2 | 16 | -2^15/2^15-1 |
int | Знаковый | 2 | 32 | -2^31/2^31-1 |
longint | Знаковый | 2 | 64 | -2^63/2^63-1 |
logic | Беззнаковый | 4 | 1 | 0/1/z/x |
reg | Беззнаковый | 4 | 1 | 0/1/z/x |
wire | Беззнаковый | 4 | 1 | 0/1/z/x |
integer | Знаковый | 4 | 32 | -2^31/2^31-1 |
short real | Знаковый | 2* | 16 | |
real | Знаковый | 2* | 32 | |
time | Беззнаковый | 4 | 64 | 0/2^64-1 |
real time | Знаковый | 2* | 32 |
И цепи и переменные могут быть перечисленными типами, кроме того можно задавать многомерные массивы связей и переменных. В таблице 2.2 представлены примеры деклараций различных типов.
Таблица 2.2 - Декларации типов
Целочисленные типы | |
shortint s; | Двоичный 16-разрядный знаковый тип |
byte unsigned b; | Двоичный 8-разрядный беззнаковый тип |
Bit [8:0] data2; | Двоичный 9-разрядный беззнаковый тип |
reg signed [7:0] int; | 8-разрядный знаковый тип (4 возможных значения на каждый разряд) |
Массивы | |
logic [7:0] data [1:5]; | Массив из 5 элементов, каждый элемент 8-разрядный вектор (4 возможных значения на каждый разряд) |
integer addr[1:10]; | Массив из 10 32-разрядных целочисленных элементов |
Структуры | |
struct { bit [3:0] Opcode; bit [7:0] Data; bit [15:0] Address; } Reg; | Декларация структуры Reg содержащей 3 элемента различных типов |
Перечислимый тип | |
enum {red, yellow, gray} Light; | Задание переменной Light безымянного перечислимого типа |
Пользовательский тип | |
typedef enum {START, STOP, WAIT} MyStateType; | Декларация пользовательского перечислимого типа MyStateType |
MyStateType state, next_state; | Задание переменных state и next_state типа MyStateType |
Встроенный тип данных string представляет собой набор (массив) символов и поддерживает создание строк переменной длины. Каждый символ в строке имеет тип byte. Первый элемент строки имеет индекс 0, т.е. строка длиной N символов будет иметь индекс 0 для первого символа строки и N-1 для последнего символа. Строки в SystemVerilog не содержат символа конца строки. В таблице приведен список встроенных методов для работы со строками.
Декларация | Назначение |
function int len() | Возвращает длину строки |
function void putc(int i, byte c) task void putc(int i, string s) | Заменяет i-й символ в строке символом c (или первым символом из строки s) |
function byte getc(int i) | Возвращает код i-го символа в строке |
function string toupper() function string tolower() | Возвращает строку преобразованную к верхнему или нижнему регистру соответственно |
function int compare(string s) | Сравнивает две строки |
function int icompare(string s) | Сравнивает две строки без учета регистра |
function int substr(int i, int j) | Возвращает подстроку начиная с i-го символа и заканчивая j-м символом строки |
function integer atoi() function integer atohex() function integer atooct() function integer atobin() | Возвращает целое число соответствующее строке в десятичной, шестнадцатеричной, восьмеричной либо двоичной форме соответственно |
function real atoreal() | Возвращает вещественное число соответствующее строке |
task itoa(integer i) task hextoa(integer i) task octtoa(integer i) task bintoa(integer i) | Преобразует целое число в строку символов в соответствующем формате |
task realtoa(integer i) | Преобразует вещественное число в строку символов |
Следует отметить, что функции atoi(), atohex(), atooct(), atobin() возвращают 32-х разрядное целое, для преобразования чисел большей разрядности нужно использовать встроенную функцию $sscanf.
Ниже приведено несколько примеров использования строк.
string str1 ; // начальное значение str1 = ""
string str2 = "Verilog";
string str3 ;
int year;
str1 = "System";
str2 = {str1, str2}; // str2 = "SystemVerilog"
str3.itoa(2016); // str3 = "2016"
year = str3.atoi(); // year = 2016
$display(str3); // "2016"
$display(str3.len); // "4"
Рассмотрим несколько примеров выражений.
8'(i + 1) - положительное десятичное число записанное перед знаком ' означает число разрядов к которому будет преобразован результат. Оператор ' также используется для того, чтобы явно задать тип переменной, который будет использоваться при интерпретации ее значение в выражении или в операторах, и называется оператором статического приведения типа переменной.
module top;
initial begin
typedef int unsigned uint;
int a = -5;
$display ( ( a <0)?"a<0":"a>=0" );
$display ( ( uint'(a)<0)?"a<0":"a>=0" );
end
endmodule
В SystemVerilog есть встроенная системная функция $cast, предназначенная для динамической проверки при моделировании возможности преобразования типов.
$cast(arg1, arg2);
Где arg1 переменная принимающая значение, arg2 - переменная или выражение, которое будет преобразовано к типу аргумента arg1.
Для создания структуры используется ключевое слово struct. Структуры предназначены для объединения различных данных в одну группу для удобства обращения и использования.
typedef struct {
bit [7:0] x;
bit [7:0] y;
} MyStruct ;
MyStruct point_coord;
Обращение к элементам структуры производится по шаблону <имя_структуры>.<имя_переменной>, например, point_coord.x. При создании структуры можно указать начальное значение.
MyStruct point_coord = '{8'h10, 8'h20};
point_coord = '{default 0}; // задание x и y равными нулю
При объявлении структур можно использовать ключевое слово packed, в этом случае поля структуры объединяются в единый регистр, позволяя проводить математические и логические операции над всей структурой.
typedef struct packed {
bit [7:0] x;
bit [7:0] y;
} MyPStruct ;
MyPStruct point_coord;
a = point_coord[15:8]; // a = point_coord.x
b = point_coord[7:0]; // b = point_coord.y
point_coord = 16'h01_02; // point_coord.x = 'h01
// point_coord.y = 'h02
SystemVerilog поддерживает массивы фиксированного размера, динамические и ассоциативные. Также массивы могут быть "упакованные" (packed array) и "неупакованными" (unpacked array). Общий синтаксис декларации массива следующий:
<DataType> <PackedDimensions> <ArrayName> <UnpackedDimensions>
где <DataType> - тип данных;
<PackedDimensions> - размерность упакованного массива;
<UnpackedDimensions> - размерность неупакованного массива;
<ArrayName> - имя декларируемого массива.
В SystemVerilog допускается объявлять массивы любых типов. Ниже приведены несколько примеров деклараций массивов.
// Одномерный неупакованный массив из 64 элементов
// типа wire
wire output_net[63:0];
// Одномерный неупакованный массив из 16 элементов
// типа logic [7:0]
logic [7:0] matrix [1:16];
// Двухмерный неупакованный массив
integer point [1:10][0:7];
При декларации массива допускается указывать только размер (число элементов массива), как это делается в языке C/C++.
// Двухмерный неупакованный массив point[0:3][0:7]
integer point [4][8];
Рассмотрим пример декларации упакованного массива:
bit [3:0][7:0] data;
В таком массиве элементы сохраняются как непрерывная последовательность данных, как показано на рисунке 2.1.
Рисунок 2.1 - Размещение элементов в упакованном массиве
В неупакованном массиве
bit [7:0] data [3:0];
данные будут храниться по отдельности независимо друг от друга, как показано на рисунке 2.
Рисунок 2.2 - Размещение элементов в неупакованном массиве
Упакованные массивы могут быть сформированы только из битовых типов, таких как logic, bit, reg wire и др. Для упакованных массивов допустимы следующие операции.
logic [3:0][7:0] data; // 2D упакованный массив
bit [31:0] bus = data; // Назначение всего массива
bit one_bit = data[2][2]; // Назначение одного бита
bit [3:0] part = data[2][5:2]; // Назначение части массива
byte B = data[3]; // Назначение 8 бит
bit [15:0] part = data[2:1]; // Назначение 16 бит
Встроенные методы для работы с массивами
В SystemVerilog предусмотрен набор встроенных методов, позволяющих выполнять различные операции над массивами либо очередями.
Метод find() возвращает все элементы, удовлетворяющие заданному выражению. Методы find_first(), find_last() возвращают первый либо последний элемент, удовлетворяющий заданному выражению, соответственно. Эти методы должны всегда использоваться совместно с оператором with, как показано в примере ниже.
int d[] = '{1, 3, 2, 4};
int r[$];
r = d.find(x) with (x <= 2); // r = { 1, 2 }
Методы min() и max() возвращают элемент с минимальным либо максимальным значением соответственно. Эти методы могут использоваться как с оператором with так и без него.
Метод reverse() устанавливает обратный порядок элементов в массиве. Метод shuffle() меняет порядок элементов случайным образом, т.е. перемешивает элементы в массиве. Эти методы не могут быть использованы с оператором with.
Методы sort() и rsort() используются для сортировки элементов массива в прямом и обратном порядке соответственно.
Методы and(), or(), xor() возвращают результат соответствующей логической операции, выполненной побитно над каждым элементом массива.
С полным перечнем доступных встроенных методов В SystemVerilog для работы с массивами можно ознакомиться в [1, 2].
Размер динамических массивов может быть задан и изменен во время выполнения моделирования. Общий формат декларации динамического массива следующий:
Тип_данных Имя_массива [ ]
Для задания или изменения размера используется оператор new[размер](Имя_массива), который имеет необязательный параметр Имя_массива, который задает имя массива который будет использоваться для инициализации начальных значений вновь созданного динамического массива. Для динамических массивов доступны два метода size() и delete(). Далее приведено несколько примеров использования динамических массивов [2].
logic [7:0] darray []; // объявление динамического массива
darray = new[100]; // создание массива из 100 элементов
darray = new[200](darray); // создание нового массива
// удвоенного размера с сохранением
// уже заданных элементов
$display("darray has %0d elements", darray.size());
darray.delete(); // удаление всех элементов массива
Ассоциативные массивы представляют собой справочник, в котором по определённому ключу можно получить соответствующее ему значение. Ассоциативные массивы широко используются для моделей больших объемов памяти с разреженным заполнением данных. Например, когда общий объем доступной памяти может быть десятки гигабайт, но в заданном тесте используется лишь небольшие блоки по несколько килобайт. В данном случае реализация модели такой памяти с помощью ассоциативных массивом будет намного удобней использования массивов с динамической или фиксированной длинной. Общий формат декларации ассоциативного массива следующий:
Тип_данных Имя_массива [ Тип_индекса ];
В качестве Тип_данных могут использоваться любые типы, разрешенные для массивом с фиксированной длинной. Тип_индекса задает тип данных, используемый для индекса. Так же вместо типа данных индекса можно задать символ *, в этом случае ассоциативный массив может индексироваться любым типом.
Для ассоциативных массивов доступны следующие встроенные методы.
Далее приведены некоторые примеры работы с ассоциативными массивам.
int CountNames [string];
CountNames["Jack"] = 1;
CountNames["Nick"] = 4;
CountNames.delete["Jack"];
Очередь это упорядоченный набор элементов переменной длинны. Очередь можно представить как неупакованный одномерный массив, который увеличивается или уменьшается автоматически. Общий формат декларации очереди следующий:
DataType PackedDimensions QueueName [$ [:Константа] ]
где PackedDimensions = [Константа:Константа]
Например,
int my_queue[$]; // очередь с неограниченным числом элементов
int fifo[$:15]; // очередь c максимальным числом элементов 16
// допустимы индексы [0:15]
В отличии от массивов, пустая очередь '{} является допустимой и может быть результатом выполнения некоторых операций.
Для очередей доступны следующие методы.
Далее приведены некоторые примеры использования очередей [2].
int Q1[$]; // Пустая очередь из элементов int
int Q2[$] = '{1, 2}; // Инициализация начальных значений
int I1, I2;
I1 = Q2[0]; // чтение первого элемента очереди
I2 = Q2[$]; // чтение последнего элемента очереди
Q2 = Q2[0:$-1]; // удаление последнего элемента очереди
Q2 = Q2[1:$-1]; // удаление первого и последнего элементов
Q.delete(i); // Эквивалентно выражению
// Q = '{Q[0:i-1],Q[i+1:$]}
Q1 = Q2; // копирование очереди Q2 в Q1
Q2 = '{Q2, 3}; // Эквивалентно Q2.push_back(3);
e = Q.pop_front(); // Соответствует: e = Q[0]; Q = Q[1:$];
e = Q.pop_back(); // Соответствует: e = Q[$]; Q = Q[0:$-1];
Q.push_front(e); // Соответствует: Q = '{e,Q};
В SystemVerilog есть шесть типов циклов: for, foreach, do-while, while, repeat и forever [2].
Цикл for предназначен для многократного выполнения одного или нескольких операторов. Для задания цикла используется ключевое слово for, после которого в круглых скобках указывается три выражения: первое - начальное назначение переменной цикла, второе - условие выполнения цикла, и третье - приращение переменной цикла.
for (int i = 0 ; i < 10; i++) begin
data[i] = 0;
end
Цикл repeat имеет следующий формат:
repeat (выражение)
оператор
и повторяет выполнение оператора заданное в выражении число раз.
repeat (5) begin
#5 clk = 1;
#5 clk = 0;
end
В цикле forever оператор (или операторы) выполняется бесконечное число раз.
forever #5 clk = ~clk;
В цикле while оператор (или операторы) выполняется до тех пор, пока условие цикла верно (либо больше 0).
a = -5
while (a < 5) begin
#5 clk = 1;
#5 clk = 0;
a = a + 1
end
Цикл do-while выполняется до тех пор пока условие выполнения цикла верно (либо больше 0), но в отличии от цикла while проверка условия происходит после выполнения оператора (или операторов) цикла, таким образом, цикл выполнится хотя бы один раз.
len = 0
do begin
. . .
len = len + 1;
end while (len < 10);
Цикл foreach используется для работы с массивами.
byte data [];
...
foreach(data[i])
data[i] = 0;
В данном примере все элементы динамического массива data примут значение 0. Оператор цикла foreach гарантирует, выполнение заданного набор операций для каждого элемента массива, при этом информации о размере массива при описания цикла явно не задается.
В SystemVerilog появилось понятие пакетов, представляющее собой именованную группу деклараций параметров, данных, типов, процедур и функций, последовательностей и свойств, которые могут быть доступны нескольким модулям, интерфейсам, программам. Пакеты объявляются с помощью ключевых свойств package-endpackage. Для подключения содержимого пакета в модуле используется ключевое слово import или оператор ::, как показано в примере ниже.
package Pkg1;
int v1;
logic [7:0] v2;
endpackage : Pkg1
package Pkg2;
int par1;
logic [7:0] v2;
endpackage : Pkg2
module PkgTest;
import Pkg1::*;
int var1 = Pkg2::par1; // Не нужно использовать
// import пакета Pkg2
logic [7:0] var2 = v2; // v2 из импортированного
// пакета Pkg1
import Pkg2::*; // Вызовет ошибку, т.к. v2 есть
// в обоих пакетах
endmodule PkgTest
Классы это специальный тип данных, позволяющий реализовать объектно-ориентированный подход в написании программ. Классы объединяют различные типы данных (называемые свойствами или атрибутами класса) и различные функции (function) и процедуры (task), называемые методами класса. Методы и свойства классов принято называть членами класса. Классы в SystemVerilog могут наследоваться от других классов. Так же как и в других объектно-ориентированных языках, можно создавать объекты класса. По своей сути классы являются ещё одним типом пользовательских данных.
В отличии от объектно-ориентированных языков программирования в SystemVerilog, чтобы реализовать верификацию цифровых блоков с помощью направленной псевдослучайной генерации тестов, в классах можно декларировать “случайные” переменные и задавать ограничения на псевдослучайную генерацию их значений.
Классы могут декларироваться в программах (program), модулях (module), пакетах (package), классах (class), интерфейсах (interface), а также в блоках generate и checker.
Ниже представлен простейший пример описания класса.
class MyFirstClass;
int data;
function int Get;
return data;
endfunction : Get
function Set (int d);
data = d;
endfunction : Set
endclass : MyFirstClass
В данном примере декларируется класс MyFirstClass, который включает свойство data и два метода Set и Get. Метод Get не имеет входных аргументов, но возвращает значение типа int. Метод Set ничего не возвращает, но имеет входной аргумент d типа int, значение которого присваивается свойству data.
Общий формат описания класса следующий.
[virtual] class [static | automatic] ClassName
[# ( [ParameterPortList] ) ]
[extends ClassType [ (ListofArguments) ] ];
[ Timeunit ]
[ ClassItems... ]
endclass [: ClassName]
В теле класса могут декларироваться следующие элементы: декларации типов (typedef), другие классы, свойства класса (переменные, константы различных типов), методы (функции и процедуры), прототипы методов, констрейны (ограничения на псевдослучайную генерацию).
По умолчанию методы класса имеют тип automatic для своих аргументов и переменных. Свойства класса могут иметь любой тип данных, кроме типов для цепей (wire), которые не совместимы с динамическим выделением данных.
В следующием примереме создаётся объект класса MyFirstClass, в данном случае MyFirstClass_h является указателем (handle) на объект класса MyFirstClass.
MyFirstClass MyFirstClass_h;
MyFirstClass_h = new;
При декларации указателя MyFirstClass_h, он принимает значение null. Для создания объекта вызывается функция new, которая выделяет память под объект класса MyFirstClass, инициализирует свойства класса в начальные значения и возвращает адрес созданного объекта. В SystemVerilog функция new создаётся автоматически, если пользователь не описал её. Ниже приведен пример описания функции new для класса MyFirstClass., в которой присваивается начальное значение для свойства data, передаваемое как входной аргумет init_value функции new.
class MyFirstClass;
int data;
function new (int init_value = 10) ;
data = init_value;
endfunction : new
. . .
endclass : MyFirstClass
В данном случае при создании объекта класса можно указать начальное значение свойства data:
MyFirstClass_h = new(100);
В SystemVerilog объект автоматически удаляется (освобождается память), если нет используемых указателей, ссылающихся на него, например
MyFirstClass MyFirstClass_h;
MyFirstClass_h = new; // Создается первый объект
MyFirstClass_h = new; // Создается второй объект
// и удаляется первый объект
MyFirstClass_h = null; // Удаляется второй объект
Для доступа к свойствам и методам класса используется символ точки (“.”), как показано ниже в примере.
MyFirstClass_h.data = 100;
MyFirstClass_h.Set(20);
data = MyFirstClass_h.Get();
В SystemVerilog свойства класса могут быть заданы как статические. Для этого при декларации свойства используется ключевое слово static перед именем типа, как показано в примере.
static int obj_number = 0;
В данном случае свойство obj_number будет общим для всех создаваемых объектов класса. Обычно такие свойства инициализируют при декларации, а не в функции new, так как new вызывается каждый раз при создании нового объекта.
При описании методов класса можно разделить декларацию метода (прототип) и описание тела метода, при этом тело метода может быть записано вне класса и даже в другом файле. Для обозначения прототипа метода используется ключевое свойство extern. При описании тела метода перед именем метода записывается имя класса, отделяемое двумя символами двоеточия (::), как показано в следующем примере.
class MyFirstClass;
int data;
external function int Get;
external virtual function Set (int d);
endclass : MyFirstClass
function int MyFirstClass::Get;
return data;
endfunction : Get
function MyFirstClass::Set (int d);
data = d;
endfunction : Set
В SystemVerilog все свойства и методы класса по умолчанию являются публичными (public). Чтобы сделать их приватными необходимо при декларации использовать ключевое слово private.
Классы могут наследоваться друг от друга, т.е. Можно создавать классы, основанные на других классах. В этом случае новый класс будет автоматически включать члены родительского класса.
class MyClass extends MyFirstClass;
function Inc(int d = 1);
data = data + d;
endfunction : Inc
function Set (int d);
if (d != 0)
data = d;
endfunction : Set
endclass : MyClass
В данном примере объявляется новый класс MyClass, наследуемый от родительского класса MyFirstClass. Класс MyClass, будет иметь свойство data и методы Get, Set, наследуемые от родителя. При этом в классе MyClass переопределяется виртуальный метод Set и добавлен метод Inc. В классе наследнике для того, чтобы явно указать, что идет обращение к членам родительского класса используется ключевое слово super, как показано в следующем примере.
super.data = 15;
super.Set(15);
Блоки позволяют группировать несколько операторов, выполнение которых рассматривается как один оператор. Есть два типа блоков:
- begin-end блок с последовательным выполнением операторов внутри блока;
- fork-join блок с параллельным выполнением операторов.
В блоке begin-end операторы выполняются в порядке их записи. Операторы в параллельных блоках выполняются одновременно: порядок их выполнения контролируется задержками и событиями. Выполнение всех операторов начинается в момент входа в блок. Поэтому задержки и события относятся к началу выполнения блока, порядок записи операторов в блоке является несущественным. После завершения выполнения всех операторов в параллельном блоке управление передается за блок fork-join [3]. Оба вида блока могут быть вложены друг в друга. Блоки могут именоваться, для этого после ключевого слова begin или fork записывается имя блока. Именные блоки являются частью иерархии проекта, по именам блоков можно обращаться по иерархии к локальным переменным, объявленным внутри блока.
fork : p1
task1();
task2();
begin
task3();
task4();
end
join : p1
В приведенном примере подпрограммы task1, task2 и последовательный блок begin-end запустятся одновременно, но task3 и task4 будут выполняться последовательно один за другим.
В SystemVerilog существует еще два вида параллельных блоков: fork-join_any и fork-join_none. Как говорилось выше блок fork-join завершается, т.е. управление передается следующему оператору после join, только после выполнения (завершения) всех процессов запущенных внутри блока fork-join. Блок fork-join_any передает управление следующему за ним оператору по завершению одного из процессов, запущенных в блоке fork. При этом не завершившиеся процессы продолжают свою работу, чтобы их отключить используется оператор disable [2]. Блок fork-join_none запускает записанные в нем процессы и сразу передает управление следующему за ним оператору.
События могут использоваться для взаимодействия или синхронизации нескольких независимых процессов или моделей. События не имеют численных значений, они имеют два состояния: выполнилось событие или нет. Момент выполнения события может контролироваться с помощью операторов @ или wait. События могут быть объявлены в модулях, блоках generate, fork-join, begin-end, функциях и подпрограммах.
Для задания того, что событие произошло, используются два оператора -> и ->>. Первый оператор является блокирующим, второй нет. Событию можно назначить другое событие или значение null. События могут сравниваться с помощью операторов ==, !=, ===, !==. События также могут передаваться в подпрограммы (task). У событий есть встроенное свойство .triggered, которое равно 1, если событие активно в текущий момент. Ниже представлен пример использования событий [2].
event StartClock, StopСlock;
always
fork
begin : ClockGenerator
clk = 0;
@StartClock forever
#5 clk = ~clk;
end
@StopClock disable ClockGenerator;
join
initial begin
. . .
-> StartClock;
. . .
-> StopClock;
end
В данном примере по срабатыванию события StartClock начинается генерация синхросигнала clk. При срабатывании события StopClock выполняется оператор disable, отключающий генерацию синхросигнала.
В SystemVerilog по сравнению с Verilog введена новая конструкция для описания портов модулей - интерфейс. С помощью интерфейсов можно группировать множество различных сигналов в единую структуру данных, при этом интерфейс рассматривается как единый порт. Кроме того интерфейс может содержать декларацию типов, процедур и функций, процедурных блоков, программных блоков, ассертов, назначение сигналов, блоки initial, always, generate, modport и др. В интерфейсе так же можно задавать различную конфигурацию портов (modport), которая определяет будет ли определенный порт входом, выходом или двунаправленным [2].
В примере ниже объявляется интерфейс apb_if, который включает семь сигналов: два входных (внешних) сигнала (clock, reset) и пять внутренних.
interface apb_if #(width = 32)
(input logic clock, reset);
logic [width-1:0] data;
logic [width-1:0] addr;
logic [3:0] error;
logic data_ready;
logic data_write;
endinterface : apb_if
Более подробно познакомиться с назначением интерфейсов можно в [2, 3].
1. IEEE Standard for SystemVerilog – Unified Hardware Design, Specification, and Verification Language / IEEE 1800-2012 . ISBN 978-0-7381-8110-3.
2. SystemVeriloig Golden Reference Guide - A concise guide to SystemVeriloig IEEE Std 1800-2009 Version 5.0, August 2012, Doulos Ltd
3. Хаханов В.И. Проектирование и верификация цифровых систем на кристаллах. Verilog & System Verilog / В.И. Хаханов, И.В. Хаханова, Е.И. Литвинова, О.А. Гузь. – Харьков : ХНУРЭ. – 2010. – 528 с.
Verilog | Язык описания поведения цифровой аппаратуры |
SystemVerilog | Расширение языка Verilog |
Динамический массив | Массив, длина которого может быть задана или изменена во время моделирования |
Ассоциативный массив | Массив, представляющий собой справочник, в котором заданному ключу соответствует определенное значение. Тип ключа может различным. |
Очередь | Это неупакованный одномерный массив переменной длины, размер которого увеличивается или уменьшается автоматически. |