SystemVerilog IEEE 1800-2023. Обзор нововведений.
Вступление
Доброго времени суток, дорогие читатели! Не могу пройти мимо значительного события в сфере микроэлектроники, а уж в сфере её верификации и подавно.
28 февраля 2024 года была опубликована новая версия стандарта языка SystemVerilog 2023. Итак, что же стандарт 2023 года принесёт нового в существующий "уклад" инженеров? Давайте разбираться!
Данный пост представляет собой краткий авторский обзор нововведений. Обзор каждого нововведения будет содержать примеры, а также ссылки на соответствующие разделы нового стандарта. Приступаем!
1. Наследование функционального покрытия
В SystemVerilog существует такое понятие, как embedded covergroup
. Если переводить дословно, то "встроенная" covergroup
. Тип такой covergroup
объявляется внутри конкретного типа класса и становится "неразрывно" с ним связанным.
Например:
class base;
enum {red, green, blue} color;
covergroup g1;
option.weight = 1;
color_cp: coverpoint color;
endgroup
function new();
g1 = new;
endfunction
endclass
Такая covergroup
по умолчанию имеет доступ ко всем полям класса и должна создаваться в функции new()
этого класса.
В новой версии стандарта появилась возможность наследовать embedded covergroup
. Наследованная covergroup "получает" доступ ко всем элементам (coverpoint
, cross
и т.д.) родительской, а также всем опциям.
Наследованная covergroup
может переопределять элементы и опции родительской, а также добавлять свои.
Например:
class derived extends base;
bit d;
covergroup extends g1;
option.weight = 1;
color_cp: coverpoint color
{
ignore_bins ignore = {blue};
}
d_cp: coverpoint d;
endgroup
function new();
super.new();
endfunction
endclass
В данном примере наследованная covegroup
(от covergroup cg1
из примера выше) переопределяет опцию weight
и точку покрытия color_cp
базовой, а также добавляет новую точку покрытия d_cp
.
Соответствующие разделы стандарта: 19.4.1.
2. Функция map() распакованного массива
В новой версии стандарта появилась новая функция map()
поэлеметной работы с распакованным массивом.
Основная суть функции заключается в итерировании по массиву и применении к каждому элементу выражения, заключенного в сопутствующую конструкцию with()
.
Например:
int A [] = {1,2,3}, B [] = {2,3,5}, C [$];
// Add one to each element of an array
A = A.map() with (item + 1'b1); // {2,3,4}
// Add the elements of 2 arrays
C = A.map(a) with (a + B[a.index]); // {4,6,9}
В данном примере при помощи функции map()
к каждому элементу массива A
сначала прибавляется 1, а после в очередь C
записывается результат поэлеметного сложения массивов A
и B
. Заметим, что функция map()
предоставляет пользователю ключевое слово item
для доступа к элементу массива и метод index
для доступа к его положению в массиве.
Также обратим внимание на важную особенность добавленной функции. Она может возвращать значение типа, отличающегося от типа обрабатываемого массива.
Например:
int A [] = {2,3,4}, B [] = {2,3,5};
// Element by element comparison
bit C [];
C = A.map(a) with (a == B[a.index]); // {1,1,0}
В данном примере происходит поэлементое сравнение массивов A
и B
. Обрабатываемый массив имеет тип int []
. Возвращаемый map()
результат имеет тип bit []
.
Соответствующие разделы стандарта: 7.12.5.
3. Множественные идентификаторы в ifdef
В новой версии стандарта был дополнен синтаксис директивы компилятора ifdef
. Ранее директива могла обрабатывать лишь одиночные идентификаторы, теперь могут обрабатываться логические выражения из множественных идентификаторов.
Пример-сравнение старой и новой версий стандарта.
В старой версии стандарта:
`ifdef A
`ifdef B
// code for AND condition
`endif
`endif
В новой версии стандарта:
`ifdef (A && B)
// code for AND condition
`endif
Обратите внимание, что в новой версии стандарта также актуальна и первая запись. А вообще - удобно! Доступные логические операторы: &&
, ||
, ->
, <->
.
Кстати, первые два нововведения (а на самом деле и все последующие после этого) относятся к несинтезируемому подмножеству. Чего не скажешь про ifdef
, который может использоваться и в синтезируемом коде. Как думаете, когда "завезут" в ПО для синтеза? Делаем ставки, господа.
Соответствующие разделы стандарта: 22.6.
4. Multiline strings
В новой версии стандарта была добавлена возможность размещать текст типа string
на нескольких строках без использования специальных символов. Для этого используются тройные кавычки """
.
Например:
$display("Humpty Dumpty sat on a wall.\n\
Humpty Dumpty had a great fall.");
Эквивалентно:
$display("""Humpty Dumpty sat on a wall.
Humpty Dumpty had a great fall. """);
То есть элемент типа string
в тройных кавычках как бы автоматически "генерирует" необходимые символы для переноса на другую строку. Интересно также, что внутри тройных кавычек можно использовать одиночные "
без специальных символов.
Например:
$display("Humpty Dumpty sat on a \"wall\".\n\
Humpty Dumpty had a great fall.");
Эквивалентно:
$display("""Humpty Dumpty sat on a "wall".
Humpty Dumpty had a great fall. """);
Вообще мне введение такого форматирования очень напомнило Python, где оно также присутствует. И кстати, оно часто используется для документирования кода. И тут я задумался...
А что, если:
function int add(int a, int b);
"""
This function adds two integers.
Arguments:
int a - first integer to add;
int b - second integer to add.
"""
return a + b;
endfunction
Соответствующие разделы стандарта: 5.9.
5. Использование чисел с плавающей точкой в функциональном покрытии
В новой версии стандарта теперь возможно определение функционального покрытия для чисел с плавающей точкой (real
, shortreal
).
Рассмотрим одну из проблем определения функционального покрытия для чисел с плавающей точкой. Разделы покрытия представляют собой конечные наборы значений. Для целых чисел такой набор может быть однозначно определен.
int a;
covergroup a_cg;
a_cp: coverpoint a {
bins b1 [] = {[0:9]}; // {0}, ..., {9}
}
endgroup
Числа же с плавающей точкой хранятся в памяти с некоторой точностью, что приводит к тому, что два разных числа могут быть представлены одним и тем же набором бит. Такая особенность может являться причиной дублирования значений в разделах покрытия, а также пересечения разделов покрытия.
real a;
covergroup a_cg;
a_cp: coverpoint a {
bins b1 [] = {[0:9]}; // ???
}
endgroup
В новой версии стандарта для поддержки использования чисел с плавающей точкой в функциональном покрытии была добавлена новая опция real_interval
, а также новый синтаксис вида +/-
и +%-
, которые призваны решить проблемы с точностью при помощи явного указания минимального шага между "соседними" числами. Давайте разбираться, как это работает.
Пример:
real a;
parameter real VALUE = 50.0;
covergroup a_cg;
option.real_interval = 0.01;
a_cp: coverpoint a {
// [49.9:50.1]
bins b1 = {[VALUE+/-0.1]};
// [49.5:50.5]
bins b2 = {[VALUE+%-1.0]};
// [0.75:0.76), [0.76:0.77), ... [0.84:0.85]
bins a1 [] = {[0.75:0.85]};
}
endgroup
В данном примере раздел b1
покрывает значения от 49.9 до 50.1. Синтаксис +/-
определяет отклонение от 50.0 на 0.1 (абсолютное значение) в обе стороны. Раздел b2
в свою очередь покрывает значения от 49.5 до 50.5. Синтаксис +%-
определяет отклонение от 50.0 на 1% (относительное значение) в обе стороны, то есть на 0.5 в абсолютном выражении.
Для массива разделов a1
количество элементов в нем определяется опцией real_interval
, которая задает шаг разбиения диапазона чисел с плавающей точкой. В данном примере шаг равен 0.01, так что интервал от 0.75 до 0.85 будет равномерно разбит на 10 интервалов: [0.85:0.86), [0.86:0.87), ..., [0.84:0.85]. Обратите внимание на включение границ в интервалы.
Соответствующие разделы стандарта: 19.5.1, 19.7.1.
6. Использование метода в качестве промежуточного результата
В большинстве симуляторов SystemVerilog в настоящее время поддерживается выполнение следующего примера:
module test;
class my_class;
function void print();
$display("Inside my_class!");
endfunction
endclass
function my_class get_my_class();
my_class cl;
cl = new();
endfunction
initial begin
get_my_class().print();
end
endmodule
Результат функции get_my_class()
используется в качестве промежуточного результата, который содержит указатель на объект типа my_class
, у которого вызывается метод print()
. В англоязычной литературе это называют "chaining of method calls".
Не трудно догадаться, что результатом выполнения будет:
Inside my_class!
Хоть данный пример и поддерживается многими симуляторами, в стандарте явно не обозначались требования к соответствующему функционалу. В новой версии наконец-то появилось соответствующее описание. Из интересного: появилось правило разграничения иерархического обращения и промежуточного результата.
Например:
class A;
int member=123;
endclass
module top;
A a;
function A F(int arg=0);
int member;
a = new();
return a;
endfunction
initial begin
$display(F.member);
$display(F().member);
end
endmodule
Результатом выполнения будет:
0
123
Новая версия стандарта явно указывает на то, что при использовании функции в качестве промежуточного результата всегда должны использоваться ()
, даже если у функции нет аргументов. В данном же примере выражение F.member
обращается к статической переменной member
функции F
, а F().member
обращается к полю member
объекта класса, который возвращается вызовом функции F
.
Соответствующие разделы стандарта: 13.4.1.
7. ref static
Для начала нам необходимо вспомнить, что переменная в SystemVerilog может быть автоматической. Если говорить простым языком, то это значит, что её присутствие в памяти на протяжении всего времени симуляции не гарантируется.
Для автоматических переменных стандартом накладываются определенные ограничения на их использование. Например, переменная не может стоять слева от <=
(быть целью Non-blocking assignment). Или, что более интересно, автоматическая переменная, объявленная вне блоков fork-join_any
и fork-join_none
не может быть использована внутри этих блоков.
Например:
initial begin
for (int j = 1; j <= 3; ++j) begin
fork
begin
automatic int k = j;
#k $write("%0t %0d\n", $time(), k);
end
join_none
end
end
Такая конструкция абсолютно легальна. Выводом будет:
1 1
2 2
3 3
А вот другой пример:
module test;
function void print(ref logic arg);
fork
forever @(arg) $display("arg changed %0t", $time());
join_none
endfunction
logic C;
initial begin
print(C);
#10; C = 1;
#10; C = 2;
end
endmodule
Который запрещен стандартом, так как для компилятора при анализе функции print()
переменная arg
потенциально может быть автоматической, а её использование внутри fork-join_none
запрещено. Обратите внимание, что, даже если переменная, передаваемая в задачу будет статической, компилятор все равно сгенерирует ошибку.
Результатом запуска примера выше в QuestaSim 2021.2 будет:
vlog test.sv
** Error (suppressible): test.sv(4): (vlog-13300)
The task or function 'print' with ref argument 'arg' must be automatic.
А вот в новой версии стандарта существует возможность передавать статическую переменную в подобную функцию по ссылке. Теперь аргумент можно будет объявить как ref static
.
Пример:
module test;
function void print(ref static logic arg);
fork
forever @(arg) $display("arg changed %0t", $time());
join_none
endfunction
logic C;
initial begin
print(C);
#10; C = 1;
#10; C = 2;
end
endmodule
Результат симуляции ожидается таким:
arg changed 10
arg changed 20
И знаете что. Симулятора, официально поддерживающего все нововведения SystemVerilog 2023 нет, а ожидаемый результат выполнения (ну почти) мне получить удалось. Спросите как?
Компиляцию первого примера в QuestaSim 2021.2 можно запустить с особыми флагами:
vlog test.sv -suppress 13300 -suppress 13219
-- Compiling module test
Errors: 0, Warnings: 0, Suppressed Errors: 1
Что это за флаги - уже другая история. Но результат симуляции был следующим:
arg changed 10
arg changed 20
Кто-то заранее "подложил соломку"? Вопрос, скорее, риторический.
Соответствующие разделы стандарта: 9.3.2, 13.5.2.
Заключение
Вот и все, дорогие читатели. Только что мы с вами погрузились в новую версию стандарта SystemVerilog. Как думаете, хороши нововведения? Спасибо вам за уделенное время! Всего наилучшего и до новых встреч!
А больше подобных обзоров вы можете найти в Telegram-канале автора Verification For All.
Post Scriptum
Если вам известны иные нововведения версии стандарта SystemVerilog 2023 года, то не стесняйтесь написать об этом в комментариях или в личные сообщения автору в Telegram. Также автору было бы интересно узнать мнение читателей по поводу того, какого еще функционала, по их мнению, не хватает в теперь уже самом актуальном стандарте языка SystemVerilog IEEE 1800-2023.